From 659493c4d2e0685577bca96c9bdb3fb7ab6c8004 Mon Sep 17 00:00:00 2001 From: Himadri Singh Date: Sun, 24 Nov 2013 20:27:34 +0530 Subject: [PATCH 001/189] Add versions to static page --- pom.xml | 3 ++- server/pom.xml | 6 ++++++ server/src/main/resources/static/index.html | 3 +++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dee4133212e..e67cd1dbdee 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,7 @@ UTF-8 0.25.0 2.1.0-incubating + 0.1.3 @@ -64,7 +65,7 @@ io.druid druid-api - 0.1.3 + ${druid.api.version} diff --git a/server/pom.xml b/server/pom.xml index 420e53cd7fc..7e45726afb8 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -229,6 +229,12 @@ + + + true + ${basedir}/src/main/resources + + maven-jar-plugin diff --git a/server/src/main/resources/static/index.html b/server/src/main/resources/static/index.html index cec3d620e88..fb8831e29ed 100644 --- a/server/src/main/resources/static/index.html +++ b/server/src/main/resources/static/index.html @@ -32,6 +32,9 @@
+
+

Druid Version: ${pom.version} Druid API Version: ${druid.api.version}

+
From abf417a1c4f9fef20e8cde8d35f0a34ebe6bc299 Mon Sep 17 00:00:00 2001 From: Himadri Singh Date: Tue, 26 Nov 2013 17:47:23 +0530 Subject: [PATCH 002/189] version of druid-server --- .../java/io/druid/server/StatusResource.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/io/druid/server/StatusResource.java b/server/src/main/java/io/druid/server/StatusResource.java index a2b30268d4d..3625641215c 100644 --- a/server/src/main/java/io/druid/server/StatusResource.java +++ b/server/src/main/java/io/druid/server/StatusResource.java @@ -24,6 +24,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; /** */ @@ -35,11 +38,33 @@ public class StatusResource public Status doGet() { return new Status( - StatusResource.class.getPackage().getImplementationVersion(), + getMavenVersion("io.druid","druid-server"), new Memory(Runtime.getRuntime()) ); } + private String getMavenVersion(String groupId, String artifactId){ + + Properties properties = new Properties(); + try { + InputStream is = StatusResource.class.getClassLoader().getResourceAsStream( + String.format( + "META-INF/maven/%s/%s/pom.properties", + groupId, + artifactId + ) + ); + if (is == null) { + return null; + } + properties.load(is); + } + catch (IOException e) { +// e.printStackTrace(); + } + return properties.getProperty("version"); + } + public static class Status { final String version; final Memory memory; From 95fafe02a7ba3637199da00f000db2a5732f8203 Mon Sep 17 00:00:00 2001 From: Himadri Singh Date: Wed, 27 Nov 2013 12:21:35 +0530 Subject: [PATCH 003/189] server versions with loaded extensions versions --- s3-extensions/pom.xml | 14 +++ .../java/io/druid/server/StatusResource.java | 87 +++++++++++++++---- .../src/main/resources/druid-server.version | 3 + 3 files changed, 87 insertions(+), 17 deletions(-) create mode 100644 server/src/main/resources/druid-server.version diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 93e0844925b..9b6f5ab3d54 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -66,4 +66,18 @@ test + + + + maven-jar-plugin + + + + true + + + + + + diff --git a/server/src/main/java/io/druid/server/StatusResource.java b/server/src/main/java/io/druid/server/StatusResource.java index 3625641215c..2f79d3564d6 100644 --- a/server/src/main/java/io/druid/server/StatusResource.java +++ b/server/src/main/java/io/druid/server/StatusResource.java @@ -20,12 +20,19 @@ package io.druid.server; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.inject.Injector; +import io.druid.initialization.DruidModule; +import io.druid.initialization.Initialization; +import io.druid.server.initialization.ExtensionsConfig; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import java.io.IOException; import java.io.InputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Properties; /** @@ -38,22 +45,44 @@ public class StatusResource public Status doGet() { return new Status( - getMavenVersion("io.druid","druid-server"), + getVersion("/druid-server.version"), + getVersion("/druid-api.version"), + getExtensionVersions(), new Memory(Runtime.getRuntime()) ); } - private String getMavenVersion(String groupId, String artifactId){ + /** + * Load the extensions list and return the implementation-versions + * + * @return map of extensions loaded with their respective implementation versions. + */ + private Map getExtensionVersions() + { + final Injector injector = Initialization.makeStartupInjector(); + final ExtensionsConfig config = injector.getInstance(ExtensionsConfig.class); + final List druidModules = Initialization.getFromExtensions(config, DruidModule.class); + Map moduleVersions = new HashMap<>(); + for (DruidModule module : druidModules) { + Package pkg = module.getClass().getPackage(); + moduleVersions.put(pkg.getImplementationTitle(), pkg.getImplementationVersion()); + } + return moduleVersions; + } + + /** + * Load properties files from the classpath and return version number + * + * @param versionFile + * + * @return version number + */ + private String getVersion(String versionFile) + { Properties properties = new Properties(); try { - InputStream is = StatusResource.class.getClassLoader().getResourceAsStream( - String.format( - "META-INF/maven/%s/%s/pom.properties", - groupId, - artifactId - ) - ); + InputStream is = StatusResource.class.getResourceAsStream(versionFile); if (is == null) { return null; } @@ -65,20 +94,42 @@ public class StatusResource return properties.getProperty("version"); } - public static class Status { - final String version; + public static class Status + { + final String serverVersion; + final String apiVersion; + final Map extensionsVersion; final Memory memory; - public Status(String version, Memory memory) + public Status( + String serverVersion, + String apiVersion, + Map extensionsVersion, + Memory memory + ) { - this.version = version; + this.serverVersion = serverVersion; + this.apiVersion = apiVersion; + this.extensionsVersion = extensionsVersion; this.memory = memory; } @JsonProperty - public String getVersion() + public String getServerVersion() { - return version; + return serverVersion; + } + + @JsonProperty + public String getApiVersion() + { + return apiVersion; + } + + @JsonProperty + public Map getExtensionsVersion() + { + return extensionsVersion; } @JsonProperty @@ -88,13 +139,15 @@ public class StatusResource } } - public static class Memory { + public static class Memory + { final long maxMemory; final long totalMemory; final long freeMemory; final long usedMemory; - public Memory(Runtime runtime) { + public Memory(Runtime runtime) + { maxMemory = runtime.maxMemory(); totalMemory = runtime.totalMemory(); freeMemory = runtime.freeMemory(); diff --git a/server/src/main/resources/druid-server.version b/server/src/main/resources/druid-server.version new file mode 100644 index 00000000000..58b62601e88 --- /dev/null +++ b/server/src/main/resources/druid-server.version @@ -0,0 +1,3 @@ +version=${pom.version} +groupId=${pom.groupId} +artifactId=${pom.artifactId} From 47278ad10965210ffa7c597a8e442ee0c855ba9c Mon Sep 17 00:00:00 2001 From: Himadri Singh Date: Wed, 4 Dec 2013 10:21:19 +0530 Subject: [PATCH 004/189] Merge relevant changes from 'druid-version-info' --- cassandra-storage/pom.xml | 16 +++++++ common/pom.xml | 10 +++- examples/pom.xml | 8 ++++ hdfs-storage/pom.xml | 16 +++++++ indexing-hadoop/pom.xml | 11 +++++ indexing-service/pom.xml | 16 +++++++ kafka-eight/pom.xml | 16 +++++++ kafka-seven/pom.xml | 15 ++++++ processing/pom.xml | 8 ++++ s3-extensions/pom.xml | 1 + server/pom.xml | 16 +++---- services/pom.xml | 12 +++++ services/src/main/java/io/druid/cli/Main.java | 10 +++- .../src/main/java/io/druid/cli/Version.java | 46 +++++++++++++++++++ 14 files changed, 191 insertions(+), 10 deletions(-) create mode 100644 services/src/main/java/io/druid/cli/Version.java diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 8a4a9f6278e..265b1ac71ba 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -53,4 +53,20 @@ test + + + + + maven-jar-plugin + + + + true + true + + + + + + diff --git a/common/pom.xml b/common/pom.xml index 7b15f631fc8..9b139d230b1 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -168,7 +168,15 @@ - + + + + true + true + + + + diff --git a/examples/pom.xml b/examples/pom.xml index 3ef0caaa856..8c510a12a78 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -104,6 +104,14 @@ + + + + true + true + + + diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index af3eb2a4140..40b122aed77 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -71,4 +71,20 @@ test + + + + + maven-jar-plugin + + + + true + true + + + + + + diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 1278df33166..f3630004a85 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -101,6 +101,17 @@ + + maven-jar-plugin + + + + true + true + + + + maven-shade-plugin diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 33361b66f2c..e21bccdd869 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -160,4 +160,20 @@ test + + + + + maven-jar-plugin + + + + true + true + + + + + + diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 53601c90cee..9df3ab928c4 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -116,4 +116,20 @@ test + + + + + maven-jar-plugin + + + + true + true + + + + + + diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index f00bdea925a..216435b0046 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -55,4 +55,19 @@ test + + + + maven-jar-plugin + + + + true + true + + + + + + diff --git a/processing/pom.xml b/processing/pom.xml index a58ffad72fc..f682c9b5877 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -133,6 +133,14 @@ + + + + true + true + + + diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 9b6f5ab3d54..da03fefb20d 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -74,6 +74,7 @@ true + true diff --git a/server/pom.xml b/server/pom.xml index 7e45726afb8..f1f34ef279b 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -224,17 +224,9 @@ caliper test - - - - - true - ${basedir}/src/main/resources - - maven-jar-plugin @@ -245,6 +237,14 @@ + + + + true + true + + + org.antlr diff --git a/services/pom.xml b/services/pom.xml index cf0f4d41d2c..fa08b5ae19e 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -51,8 +51,20 @@ ${project.parent.version} + + + maven-jar-plugin + + + + true + true + + + + org.apache.maven.plugins maven-shade-plugin diff --git a/services/src/main/java/io/druid/cli/Main.java b/services/src/main/java/io/druid/cli/Main.java index 551acea0706..91b56a83977 100644 --- a/services/src/main/java/io/druid/cli/Main.java +++ b/services/src/main/java/io/druid/cli/Main.java @@ -27,6 +27,8 @@ import io.druid.cli.convert.ConvertProperties; import io.druid.cli.validate.DruidJsonValidator; import io.druid.initialization.Initialization; import io.druid.server.initialization.ExtensionsConfig; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; import java.util.List; @@ -41,7 +43,13 @@ public class Main builder.withDescription("Druid command-line runner.") .withDefaultCommand(Help.class) - .withCommands(Help.class); + .withCommands(Help.class, Version.class); + + Runnable cmd = builder.build().parse(args); + if (cmd instanceof Version) { + Logger.getRootLogger().setLevel(Level.OFF); + } + builder.withGroup("server") .withDescription("Run one of the Druid server types.") diff --git a/services/src/main/java/io/druid/cli/Version.java b/services/src/main/java/io/druid/cli/Version.java new file mode 100644 index 00000000000..40335deb1b7 --- /dev/null +++ b/services/src/main/java/io/druid/cli/Version.java @@ -0,0 +1,46 @@ +package io.druid.cli; + +import io.airlift.command.Command; +import io.druid.initialization.DruidModule; +import io.druid.initialization.Initialization; +import io.druid.server.initialization.ExtensionsConfig; + +@Command( + name = "version", + description = "Returns Druid version information" +) +public class Version implements Runnable { + @Override + public void run() + { + System.out.println("Druid version " + Initialization.class.getPackage().getImplementationVersion()); + System.out.println("Druid API version " + DruidModule.class.getPackage().getImplementationVersion()); + + ExtensionsConfig config = Initialization.makeStartupInjector().getInstance(ExtensionsConfig.class); + + System.out.println(""); + System.out.println("Registered Druid Modules"); + for (DruidModule module : Initialization.getFromExtensions(config, DruidModule.class)) { + String artifact = module.getClass().getPackage().getImplementationTitle(); + String version = module.getClass().getPackage().getImplementationVersion(); + + if(artifact != null) { + System.out.println( + String.format( + " - %s (%s-%s)", + module.getClass().getCanonicalName(), + module.getClass().getPackage().getImplementationTitle(), + module.getClass().getPackage().getImplementationVersion() + ) + ); + } else { + System.out.println( + String.format( + " - %s", + module.getClass().getCanonicalName() + ) + ); + } + } + } +} From a2c83887421b87d1053594c043b8dc3902112513 Mon Sep 17 00:00:00 2001 From: Himadri Singh Date: Wed, 4 Dec 2013 10:21:36 +0530 Subject: [PATCH 005/189] No need of druid-server.version --- server/src/main/java/io/druid/server/StatusResource.java | 2 +- server/src/main/resources/druid-server.version | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 server/src/main/resources/druid-server.version diff --git a/server/src/main/java/io/druid/server/StatusResource.java b/server/src/main/java/io/druid/server/StatusResource.java index 2f79d3564d6..59c6da0f4b1 100644 --- a/server/src/main/java/io/druid/server/StatusResource.java +++ b/server/src/main/java/io/druid/server/StatusResource.java @@ -45,7 +45,7 @@ public class StatusResource public Status doGet() { return new Status( - getVersion("/druid-server.version"), + Initialization.class.getPackage().getImplementationVersion(), getVersion("/druid-api.version"), getExtensionVersions(), new Memory(Runtime.getRuntime()) diff --git a/server/src/main/resources/druid-server.version b/server/src/main/resources/druid-server.version deleted file mode 100644 index 58b62601e88..00000000000 --- a/server/src/main/resources/druid-server.version +++ /dev/null @@ -1,3 +0,0 @@ -version=${pom.version} -groupId=${pom.groupId} -artifactId=${pom.artifactId} From 32200b9b41300d52826b4c73707801a7d24db308 Mon Sep 17 00:00:00 2001 From: Himadri Singh Date: Wed, 4 Dec 2013 11:00:47 +0530 Subject: [PATCH 006/189] removing debug line. --- services/src/main/java/io/druid/cli/Version.java | 1 - 1 file changed, 1 deletion(-) diff --git a/services/src/main/java/io/druid/cli/Version.java b/services/src/main/java/io/druid/cli/Version.java index 40335deb1b7..0ec5298a1bf 100644 --- a/services/src/main/java/io/druid/cli/Version.java +++ b/services/src/main/java/io/druid/cli/Version.java @@ -14,7 +14,6 @@ public class Version implements Runnable { public void run() { System.out.println("Druid version " + Initialization.class.getPackage().getImplementationVersion()); - System.out.println("Druid API version " + DruidModule.class.getPackage().getImplementationVersion()); ExtensionsConfig config = Initialization.makeStartupInjector().getInstance(ExtensionsConfig.class); From e6b915f1e7a42db676267d0efa23dd60d7c4ab15 Mon Sep 17 00:00:00 2001 From: Himadri Singh Date: Tue, 10 Dec 2013 00:05:56 +0530 Subject: [PATCH 007/189] druid-api version not required --- .../java/io/druid/server/StatusResource.java | 36 +------------------ 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/server/src/main/java/io/druid/server/StatusResource.java b/server/src/main/java/io/druid/server/StatusResource.java index 59c6da0f4b1..8fb6733d97f 100644 --- a/server/src/main/java/io/druid/server/StatusResource.java +++ b/server/src/main/java/io/druid/server/StatusResource.java @@ -46,14 +46,13 @@ public class StatusResource { return new Status( Initialization.class.getPackage().getImplementationVersion(), - getVersion("/druid-api.version"), getExtensionVersions(), new Memory(Runtime.getRuntime()) ); } /** - * Load the extensions list and return the implementation-versions + * Load the unique extensions and return their implementation-versions * * @return map of extensions loaded with their respective implementation versions. */ @@ -70,46 +69,19 @@ public class StatusResource return moduleVersions; } - /** - * Load properties files from the classpath and return version number - * - * @param versionFile - * - * @return version number - */ - private String getVersion(String versionFile) - { - - Properties properties = new Properties(); - try { - InputStream is = StatusResource.class.getResourceAsStream(versionFile); - if (is == null) { - return null; - } - properties.load(is); - } - catch (IOException e) { -// e.printStackTrace(); - } - return properties.getProperty("version"); - } - public static class Status { final String serverVersion; - final String apiVersion; final Map extensionsVersion; final Memory memory; public Status( String serverVersion, - String apiVersion, Map extensionsVersion, Memory memory ) { this.serverVersion = serverVersion; - this.apiVersion = apiVersion; this.extensionsVersion = extensionsVersion; this.memory = memory; } @@ -120,12 +92,6 @@ public class StatusResource return serverVersion; } - @JsonProperty - public String getApiVersion() - { - return apiVersion; - } - @JsonProperty public Map getExtensionsVersion() { From 8117fff4e20a8aeb1cf3fb5966c219054ccb348c Mon Sep 17 00:00:00 2001 From: Himadri Singh Date: Tue, 10 Dec 2013 00:39:10 +0530 Subject: [PATCH 008/189] Some minor fixes --- .../src/main/java/io/druid/cli/Version.java | 57 ++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/services/src/main/java/io/druid/cli/Version.java b/services/src/main/java/io/druid/cli/Version.java index 0ec5298a1bf..89306fc42fc 100644 --- a/services/src/main/java/io/druid/cli/Version.java +++ b/services/src/main/java/io/druid/cli/Version.java @@ -1,3 +1,22 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + package io.druid.cli; import io.airlift.command.Command; @@ -5,41 +24,41 @@ import io.druid.initialization.DruidModule; import io.druid.initialization.Initialization; import io.druid.server.initialization.ExtensionsConfig; +import java.lang.StringBuilder; + @Command( name = "version", description = "Returns Druid version information" ) -public class Version implements Runnable { +public class Version implements Runnable +{ + private static final String NL = "\n"; @Override public void run() { - System.out.println("Druid version " + Initialization.class.getPackage().getImplementationVersion()); + StringBuilder output = new StringBuilder(); + output.append("Druid version ").append(NL); + output.append(Initialization.class.getPackage().getImplementationVersion()).append(NL).append(NL); ExtensionsConfig config = Initialization.makeStartupInjector().getInstance(ExtensionsConfig.class); - System.out.println(""); - System.out.println("Registered Druid Modules"); + output.append("Registered Druid Modules").append(NL); + for (DruidModule module : Initialization.getFromExtensions(config, DruidModule.class)) { String artifact = module.getClass().getPackage().getImplementationTitle(); String version = module.getClass().getPackage().getImplementationVersion(); - if(artifact != null) { - System.out.println( - String.format( - " - %s (%s-%s)", - module.getClass().getCanonicalName(), - module.getClass().getPackage().getImplementationTitle(), - module.getClass().getPackage().getImplementationVersion() - ) - ); + if (artifact != null) { + output.append( + String.format(" - %s (%s-%s)", module.getClass().getCanonicalName(), artifact, version) + ).append(NL); } else { - System.out.println( - String.format( - " - %s", - module.getClass().getCanonicalName() - ) - ); + output.append( + String.format(" - %s", module.getClass().getCanonicalName()) + ).append(NL); } } + + System.out.println(output.toString()); } } From b5f6dbc32f9fdd3745f78da9ea7353c308de4b1b Mon Sep 17 00:00:00 2001 From: Himadri Singh Date: Tue, 10 Dec 2013 02:09:41 +0530 Subject: [PATCH 009/189] Code refactoring, one place! --- .../java/io/druid/server/StatusResource.java | 117 +++++++++++++++--- services/src/main/java/io/druid/cli/Main.java | 6 - .../src/main/java/io/druid/cli/Version.java | 32 +---- 3 files changed, 99 insertions(+), 56 deletions(-) diff --git a/server/src/main/java/io/druid/server/StatusResource.java b/server/src/main/java/io/druid/server/StatusResource.java index 8fb6733d97f..f2c01050496 100644 --- a/server/src/main/java/io/druid/server/StatusResource.java +++ b/server/src/main/java/io/druid/server/StatusResource.java @@ -28,12 +28,8 @@ import io.druid.server.initialization.ExtensionsConfig; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; +import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.Properties; /** */ @@ -43,6 +39,11 @@ public class StatusResource @GET @Produces("application/json") public Status doGet() + { + return getStatus(); + } + + public static Status getStatus() { return new Status( Initialization.class.getPackage().getImplementationVersion(), @@ -56,46 +57,55 @@ public class StatusResource * * @return map of extensions loaded with their respective implementation versions. */ - private Map getExtensionVersions() + private static List getExtensionVersions() { final Injector injector = Initialization.makeStartupInjector(); final ExtensionsConfig config = injector.getInstance(ExtensionsConfig.class); final List druidModules = Initialization.getFromExtensions(config, DruidModule.class); - Map moduleVersions = new HashMap<>(); + + List moduleVersions = new ArrayList<>(); for (DruidModule module : druidModules) { - Package pkg = module.getClass().getPackage(); - moduleVersions.put(pkg.getImplementationTitle(), pkg.getImplementationVersion()); + + String artifact = module.getClass().getPackage().getImplementationTitle(); + String version = module.getClass().getPackage().getImplementationVersion(); + + ModuleVersion moduleVersion; + if (artifact != null) { + moduleVersion = new ModuleVersion(module.getClass().getCanonicalName(), artifact, version); + } else { + moduleVersion = new ModuleVersion(module.getClass().getCanonicalName()); + } + + moduleVersions.add(moduleVersion); } return moduleVersions; } public static class Status { - final String serverVersion; - final Map extensionsVersion; + final String version; + final List modules; final Memory memory; public Status( - String serverVersion, - Map extensionsVersion, - Memory memory + String version, List modules, Memory memory ) { - this.serverVersion = serverVersion; - this.extensionsVersion = extensionsVersion; + this.version = version; + this.modules = modules; this.memory = memory; } @JsonProperty - public String getServerVersion() + public String getVersion() { - return serverVersion; + return version; } @JsonProperty - public Map getExtensionsVersion() + public List getModules() { - return extensionsVersion; + return modules; } @JsonProperty @@ -103,6 +113,72 @@ public class StatusResource { return memory; } + + @Override + public String toString() + { + final String NL = "\n"; + StringBuilder output = new StringBuilder(); + output.append(String.format("Druid version - %s", version)).append(NL).append(NL); + + if (modules.size() > 0) { + output.append("Registered Druid Modules").append(NL); + } else { + output.append("No Druid Modules loaded !"); + } + + for (ModuleVersion moduleVersion : modules) { + output.append(moduleVersion).append(NL); + } + return output.toString(); + } + } + + public static class ModuleVersion + { + final String name; + final String artifact; + final String version; + + public ModuleVersion(String name) + { + this(name, "", ""); + } + + public ModuleVersion(String name, String artifact, String version) + { + this.name = name; + this.artifact = artifact; + this.version = version; + } + + @JsonProperty + public String getName() + { + return name; + } + + @JsonProperty + public String getArtifact() + { + return artifact; + } + + @JsonProperty + public String getVersion() + { + return version; + } + + @Override + public String toString() + { + if (artifact.isEmpty()) { + return String.format(" - %s ", name); + } else { + return String.format(" - %s (%s-%s)", name, artifact, version); + } + } } public static class Memory @@ -143,5 +219,6 @@ public class StatusResource { return usedMemory; } + } } diff --git a/services/src/main/java/io/druid/cli/Main.java b/services/src/main/java/io/druid/cli/Main.java index 91b56a83977..7e9a633e8ed 100644 --- a/services/src/main/java/io/druid/cli/Main.java +++ b/services/src/main/java/io/druid/cli/Main.java @@ -45,12 +45,6 @@ public class Main .withDefaultCommand(Help.class) .withCommands(Help.class, Version.class); - Runnable cmd = builder.build().parse(args); - if (cmd instanceof Version) { - Logger.getRootLogger().setLevel(Level.OFF); - } - - builder.withGroup("server") .withDescription("Run one of the Druid server types.") .withDefaultCommand(Help.class) diff --git a/services/src/main/java/io/druid/cli/Version.java b/services/src/main/java/io/druid/cli/Version.java index 89306fc42fc..9212a3cb800 100644 --- a/services/src/main/java/io/druid/cli/Version.java +++ b/services/src/main/java/io/druid/cli/Version.java @@ -20,11 +20,7 @@ package io.druid.cli; import io.airlift.command.Command; -import io.druid.initialization.DruidModule; -import io.druid.initialization.Initialization; -import io.druid.server.initialization.ExtensionsConfig; - -import java.lang.StringBuilder; +import io.druid.server.StatusResource; @Command( name = "version", @@ -32,33 +28,9 @@ import java.lang.StringBuilder; ) public class Version implements Runnable { - private static final String NL = "\n"; @Override public void run() { - StringBuilder output = new StringBuilder(); - output.append("Druid version ").append(NL); - output.append(Initialization.class.getPackage().getImplementationVersion()).append(NL).append(NL); - - ExtensionsConfig config = Initialization.makeStartupInjector().getInstance(ExtensionsConfig.class); - - output.append("Registered Druid Modules").append(NL); - - for (DruidModule module : Initialization.getFromExtensions(config, DruidModule.class)) { - String artifact = module.getClass().getPackage().getImplementationTitle(); - String version = module.getClass().getPackage().getImplementationVersion(); - - if (artifact != null) { - output.append( - String.format(" - %s (%s-%s)", module.getClass().getCanonicalName(), artifact, version) - ).append(NL); - } else { - output.append( - String.format(" - %s", module.getClass().getCanonicalName()) - ).append(NL); - } - } - - System.out.println(output.toString()); + System.out.println(StatusResource.getStatus()); } } From 84234b142d1c89f6beaf152d0f1d55544d795e45 Mon Sep 17 00:00:00 2001 From: Igal Levy Date: Mon, 9 Dec 2013 13:21:57 -0800 Subject: [PATCH 010/189] added title; minor typo fixes --- docs/content/Configuration.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/content/Configuration.md b/docs/content/Configuration.md index 51afb07ae65..1934b941bc3 100644 --- a/docs/content/Configuration.md +++ b/docs/content/Configuration.md @@ -1,25 +1,26 @@ --- layout: doc_page --- + +# Configuring Druid + This describes the basic server configuration that is loaded by all the server processes; the same file is loaded by all. See also the json "specFile" descriptions in [Realtime](Realtime.html) and [Batch-ingestion](Batch-ingestion.html). -JVM Configuration Best Practices -================================ +## JVM Configuration Best Practices There are three JVM parameters that we set on all of our processes: -1. `-Duser.timezone=UTC` This sets the default timezone of the JVM to UTC. We always set this and do not test with other default timezones, so local timezones might work, but they also might uncover weird and interesting bugs -2. `-Dfile.encoding=UTF-8` This is similar to timezone, we test assuming UTF-8. Local encodings might work, but they also might result in weird and interesting bugs -3. `-Djava.io.tmpdir=` Various parts of the system that interact with the file system do it via temporary files, these files can get somewhat large. Many production systems are setup to have small (but fast) `/tmp` directories, these can be problematic with Druid so we recommend pointing the JVM’s tmp directory to something with a little more meat. +1. `-Duser.timezone=UTC` This sets the default timezone of the JVM to UTC. We always set this and do not test with other default timezones, so local timezones might work, but they also might uncover weird and interesting bugs. +2. `-Dfile.encoding=UTF-8` This is similar to timezone, we test assuming UTF-8. Local encodings might work, but they also might result in weird and interesting bugs. +3. `-Djava.io.tmpdir=` Various parts of the system that interact with the file system do it via temporary files, and these files can get somewhat large. Many production systems are set up to have small (but fast) `/tmp` directories, which can be problematic with Druid so we recommend pointing the JVM’s tmp directory to something with a little more meat. -Modules -======= +## Modules As of Druid v0.6, most core Druid functionality has been compartmentalized into modules. There are a set of default modules that may apply to any node type, and there are specific modules for the different node types. Default modules are __lazily instantiated__. Each module has its own set of configuration. This page will describe the configuration of the default modules. Configuration of the various modules is done via Java properties. These can either be provided as `-D` system properties on the java command line or they can be passed in via a file called `runtime.properties` that exists on the classpath. -Note: as a future item, we’d like to consolidate all of the various configuration into a yaml/JSON based configuration files. +Note: as a future item, we’d like to consolidate all of the various configuration into a yaml/JSON based configuration file. ### Emitter Module @@ -147,7 +148,7 @@ Druid storage nodes maintain information about segments they have already downlo |Property|Description|Default| |--------|-----------|-------| -|`druid.segmentCache.locations`|Segments assigned to a historical node are first stored on the local file system and then served by the historical node. These locations defines where that local cache resides|none| +|`druid.segmentCache.locations`|Segments assigned to a historical node are first stored on the local file system and then served by the historical node. These locations define where that local cache resides|none| |`druid.segmentCache.deleteOnRemove`|Delete segment files from cache once a node is no longer serving a segment.|true| |`druid.segmentCache.infoDir`|Historical nodes keep track of the segments they are serving so that when the process is restarted they can reload the same segments without waiting for the coordinator to reassign. This path defines where this metadata is kept. Directory will be created if needed.|${first_location}/info_dir| From ddbab46ace9c09453d190b25c8d98ced2bc103e0 Mon Sep 17 00:00:00 2001 From: Igal Levy Date: Mon, 9 Dec 2013 14:03:29 -0800 Subject: [PATCH 011/189] added title; fixed link to configuration --- docs/content/Cluster-setup.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/content/Cluster-setup.md b/docs/content/Cluster-setup.md index aa142efc453..e4ba0e564f1 100644 --- a/docs/content/Cluster-setup.md +++ b/docs/content/Cluster-setup.md @@ -1,6 +1,9 @@ --- layout: doc_page --- + +# Setting Up a Druid Cluster + A Druid cluster consists of various node types that need to be set up depending on your use case. See our [Design](Design.html) docs for a description of the different node types. Minimum Physical Layout: Absolute Minimum @@ -74,7 +77,7 @@ Local disk ("ephemeral" on AWS EC2) for caching is recommended over network moun Setup ----- -Setting up a cluster is essentially just firing up all of the nodes you want with the proper [[configuration]]. One thing to be aware of is that there are a few properties in the configuration that potentially need to be set individually for each process: +Setting up a cluster is essentially just firing up all of the nodes you want with the proper [configuration](Configuration.html). One thing to be aware of is that there are a few properties in the configuration that potentially need to be set individually for each process: ``` druid.server.type=historical|realtime From 0e3378e5cd762cb354913fce6c53b6503259bc29 Mon Sep 17 00:00:00 2001 From: Igal Levy Date: Mon, 9 Dec 2013 15:17:56 -0800 Subject: [PATCH 012/189] refactored sidebar sections to group related topics together under a section that encompasses what they are about, and communicates to a user that this is the go-to section for provisioning/deploying etc. a druid cluster --- docs/content/toc.textile | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/docs/content/toc.textile b/docs/content/toc.textile index 84fde036997..3b058a95c2c 100644 --- a/docs/content/toc.textile +++ b/docs/content/toc.textile @@ -14,15 +14,11 @@ h2. Getting Started * "Tutorial: Loading Your Data Part 2":./Tutorial:-Loading-Your-Data-Part-2.html * "Tutorial: All About Queries":./Tutorial:-All-About-Queries.html -h2. Evaluate Druid +h2. Operations * "Cluster Setup":./Cluster-setup.html -* "Booting a Production Cluster":./Booting-a-production-cluster.html - -h2. Configuration * "Configuration":Configuration.html - -h2. Extend Druid -* "Modules":./Modules.html +* "Extending Druid":./Modules.html +* "Booting a Production Cluster":./Booting-a-production-cluster.html h2. Data Ingestion * "Realtime":./Realtime.html From 19be4f8bb0217ecee986d30f59c15b38e5263281 Mon Sep 17 00:00:00 2001 From: Igal Levy Date: Mon, 9 Dec 2013 16:08:14 -0800 Subject: [PATCH 013/189] Refactored modules section for better flow/readability and added link to extending druid --- docs/content/Configuration.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/content/Configuration.md b/docs/content/Configuration.md index 1934b941bc3..47c495f0142 100644 --- a/docs/content/Configuration.md +++ b/docs/content/Configuration.md @@ -16,7 +16,9 @@ There are three JVM parameters that we set on all of our processes: ## Modules -As of Druid v0.6, most core Druid functionality has been compartmentalized into modules. There are a set of default modules that may apply to any node type, and there are specific modules for the different node types. Default modules are __lazily instantiated__. Each module has its own set of configuration. This page will describe the configuration of the default modules. +As of Druid v0.6, most core Druid functionality has been compartmentalized into modules. There are a set of default modules that may apply to any node type, and there are specific modules for the different node types. Default modules are __lazily instantiated__. Each module has its own set of configuration. + +This page describes the configuration of the default modules. Node-specific configuration is discussed on each node's respective page. In addition, you can add custom modules to [extend Druid](Modules.html). Configuration of the various modules is done via Java properties. These can either be provided as `-D` system properties on the java command line or they can be passed in via a file called `runtime.properties` that exists on the classpath. From fcc8801b13214b651c5c2da665f31548b2e3ca32 Mon Sep 17 00:00:00 2001 From: Igal Levy Date: Mon, 9 Dec 2013 16:11:06 -0800 Subject: [PATCH 014/189] added title --- docs/content/Modules.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/content/Modules.md b/docs/content/Modules.md index 17b8e538785..b5b8a693053 100644 --- a/docs/content/Modules.md +++ b/docs/content/Modules.md @@ -1,6 +1,9 @@ --- layout: doc_page --- + +# Extending Druid With Custom Modules + Druid version 0.6 introduces a new module system that allows for the addition of extensions at runtime. ## Specifying extensions @@ -164,4 +167,4 @@ Adding new Jersey resources to a module requires calling the following code to b ```java Jerseys.addResource(binder, NewResource.class); -``` \ No newline at end of file +``` From 867347658f0789bbfe33762a1d1c227d243fbdbb Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 9 Dec 2013 17:19:17 -0800 Subject: [PATCH 015/189] some spell check fixes for paper --- publications/whitepaper/druid.pdf | Bin 540067 -> 540245 bytes publications/whitepaper/druid.tex | 53 +++++++++++++++--------------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/publications/whitepaper/druid.pdf b/publications/whitepaper/druid.pdf index 3fe978b76611f34fca3de4eddc77a276bd691e01..6044d7d9c2411fc94ad7758b8aa1ff2b842f28c7 100644 GIT binary patch delta 108166 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

p_R&@8A|YUmu?bac=DYP(Q*Z0w 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 BFMI$1 diff --git a/publications/whitepaper/druid.tex b/publications/whitepaper/druid.tex index 0c040a70b4d..1369c46a3fa 100644 --- a/publications/whitepaper/druid.tex +++ b/publications/whitepaper/druid.tex @@ -144,14 +144,14 @@ applications \cite{tschetter2011druid}. In the early days of Metamarkets, we were focused on building a hosted dashboard that would allow users to arbitrary explore and visualize event streams. The data store powering the dashboard needed to return queries fast enough that the data visualizations built on top -of it could update provide users with an interactive experience. +of it could provide users with an interactive experience. In addition to the query latency needs, the system had to be multi-tenant and highly available. The Metamarkets product is used in a highly concurrent environment. Downtime is costly and many businesses cannot afford to wait if a system is unavailable in the face of software upgrades or network failure. -Downtime for startups, who often do not have internal operations teams, can -determine whether a business succeeds or fails. +Downtime for startups, who often lack proper internal operations management, can +determine business success or failure. Finally, another key problem that Metamarkets faced in its early days was to allow users and alerting systems to be able to make business decisions in @@ -170,15 +170,15 @@ analytics platform in multiple companies. \label{sec:architecture} A Druid cluster consists of different types of nodes and each node type is designed to perform a specific set of things. We believe this design separates -concerns and simplifies the complexity of the system. There is minimal -interaction between the different node types and hence, intra-cluster -communication failures have minimal impact on data availability. The different -node types operate fairly independent of each other and to solve complex data -analysis problems, they come together to form a fully working system. -The name Druid comes from the Druid class in many role-playing games: it is a -shape-shifter, capable of taking on many different forms to fulfill various -different roles in a group. The composition of and flow of data in a Druid -cluster are shown in Figure~\ref{fig:cluster}. +concerns and simplifies the complexity of the system. The different node types +operate fairly independent of each other and there is minimal interaction +between them. Hence, intra-cluster communication failures have minimal impact +on data availability. To solve complex data analysis problems, the different +node types come together to form a fully working system. The name Druid comes +from the Druid class in many role-playing games: it is a shape-shifter, capable +of taking on many different forms to fulfill various different roles in a +group. The composition of and flow of data in a Druid cluster are shown in +Figure~\ref{fig:cluster}. \begin{figure*} \centering @@ -213,10 +213,10 @@ still be queried. Figure~\ref{fig:realtime_flow} illustrates the process. \begin{figure} \centering \includegraphics[width = 2.8in]{realtime_flow} -\caption{Real-time nodes first buffer events in memory. After some period of -time, in-memory indexes are persisted to disk. After another period of time, -all persisted indexes are merged together and handed off. Queries on data hit -the in-memory index and the persisted indexes.} +\caption{Real-time nodes first buffer events in memory. On a periodic basis, +the in-memory index is persisted to disk. On another periodic basis, all +persisted indexes are merged together and handed off. Queries for data will hit the +in-memory index and the persisted indexes.} \label{fig:realtime_flow} \end{figure} @@ -332,7 +332,7 @@ serves whatever data it finds. Historical nodes can support read consistency because they only deal with immutable data. Immutable data blocks also enable a simple parallelization -model: historical nodes can scan and aggregate immutable blocks concurrently +model: historical nodes can concurrently scan and aggregate immutable blocks without blocking. \subsubsection{Tiers} @@ -385,7 +385,7 @@ caching the results would be unreliable. \includegraphics[width = 4.5in]{caching} \caption{Broker nodes cache per segment results. Every Druid query is mapped to a set of segments. Queries often combine cached segment results with those that -need tobe computed on historical and real-time nodes.} +need to be computed on historical and real-time nodes.} \label{fig:caching} \end{figure*} @@ -399,7 +399,7 @@ nodes are unable to communicate to Zookeeper, they use their last known view of the cluster and continue to forward queries to real-time and historical nodes. Broker nodes make the assumption that the structure of the cluster is the same as it was before the outage. In practice, this availability model has allowed -our Druid cluster to continue serving queries for several hours while we +our Druid cluster to continue serving queries for a significant period of time while we diagnosed Zookeeper outages. \subsection{Coordinator Nodes} @@ -564,9 +564,9 @@ In this case, we compress the raw values as opposed to their dictionary representations. \subsection{Indices for Filtering Data} -In most real world OLAP workflows, queries are issued for the aggregated -results for some set of metrics where some set of dimension specifications are -met. An example query may ask "How many Wikipedia edits were done by users in +In many real world OLAP workflows, queries are issued for the aggregated +results of some set of metrics where some set of dimension specifications are +met. An example query may be asked is: "How many Wikipedia edits were done by users in San Francisco who are also male?". This query is filtering the Wikipedia data set in Table~\ref{tab:sample_data} based on a Boolean expression of dimension values. In many real world data sets, dimension columns contain strings and @@ -712,7 +712,7 @@ equal to "Ke\$ha". The results will be bucketed by day and will be a JSON array Druid supports many types of aggregations including double sums, long sums, minimums, maximums, and several others. Druid also supports complex aggregations -such as cardinality estimation and approxmiate quantile estimation. The +such as cardinality estimation and approximate quantile estimation. The results of aggregations can be combined in mathematical expressions to form other aggregations. The query API is highly customizable and can be extended to filter and group results based on almost any arbitrary condition. It is beyond @@ -892,10 +892,9 @@ support computation directly in the storage layer. There are also other data stores designed for some of the same of the data warehousing issues that Druid is meant to solve. These systems include include in-memory databases such as SAP’s HANA \cite{farber2012sap} and VoltDB \cite{voltdb2010voltdb}. These data -stores lack Druid's low latency ingestion characteristics. Similar to -\cite{paraccel2013}, Druid has analytical features built in, however, it is -much easier to do system wide rolling software updates in Druid (with no -downtime). +stores lack Druid's low latency ingestion characteristics. Druid also has +native analytical features baked in, similar to \cite{paraccel2013}, however, +Druid allows system wide rolling software updates with no downtime. Druid's low latency data ingestion features share some similarities with Trident/Storm \cite{marz2013storm} and Streaming Spark From 9d4f18d12b33de8b44dee4880789518c9ca37b42 Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 9 Dec 2013 17:57:29 -0800 Subject: [PATCH 016/189] fix figures --- publications/whitepaper/druid.tex | 2 +- .../figures/historical_download.png | Bin 28917 -> 28808 bytes .../whitepaper/figures/realtime_flow.png | Bin 52423 -> 52345 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/publications/whitepaper/druid.tex b/publications/whitepaper/druid.tex index 1369c46a3fa..4554a53bc6a 100644 --- a/publications/whitepaper/druid.tex +++ b/publications/whitepaper/druid.tex @@ -326,7 +326,7 @@ serves whatever data it finds. \begin{figure} \centering \includegraphics[width = 2.8in]{historical_download} -\caption{Historical nodes download immutable segments from deep storage.} +\caption{Historical nodes download immutable segments from deep storage. Segments must be loaded in memory before they can be queried.} \label{fig:historical_download} \end{figure} diff --git a/publications/whitepaper/figures/historical_download.png b/publications/whitepaper/figures/historical_download.png index 416d44bfc6c5f2d301323b4e2923d703c6e33db3..a9db3fd3609a9384a32eb14993bda915af6f996f 100644 GIT binary patch literal 28808 zcmc$`1ys~sv^NY0GJq&UDkvoa5+Wra-5^rZA*i5~bmsulNC*njsdNcQ2}6gp#E5iA z4c$H88PMlG_kQ<&>;2yKuJz7Z;;fnf*=O&4_StoQXM$CfWC`&p@i8zk2<7FZo?u{H z!otA7a)4Y0pQIJP@58{bQI?kyS9kffQcK{X@jI!aRvxRt2A>jBfEl7r+DvN~`6i5( z9!FeU$)9r6;h7(c?(%(neD%95ESK=pC8BBZmp)3C@LL5tJ%h3lc1fRF#1FEOPb50ecx z-?wnHCfiBK$(U51y59@Sx|78ZxKa_n|1cSMc7&=|uREUiSmDG_64EizjOszLpRE&Y zvW1v1XGvgSc#owrXbycxsi&vIv$H=4AiJnvZKOVbHREUTxW#e1ix4p>0qH1y-%tA=T`*lV|=Z;*ute=dA0@B=7rf; zWNoH$|MkQ{nXBu9xSH#R<#tig={usRPp-9Fo~1}!QEm&GpT@70Souu$7jMP*qJmI0 z4>fs(h=2%OAD5i2i`l&6U}NLC9T>sI8dLD#*h%Zjb*MyFGKC>;DEU1Cw<|AF2x*>V zG|WQ4a)`}y-c5ewtg4llqKjd+$>cW}WAe3@^a_)mWDo zW~80t5LaszR3#y1eZ_QyOb%fLI5;@)x2Vyi0d~NHTdE_<`KP8Jw{=f91cQztM+ZKS zH}J`xZ&Z*dpNl70jRQP1@PjFt&%xHp(8Ij==$bSE9Rrzw!<3@^NVe@6izKT6&Ls>C z9zxx1y;-O3D$gUFrw630ev#_3O;>AAAzzz6@&T^;vb&rM?0)Tp4tYc9!s6HyoUjwF zwdQJ)|GJS!nGOL@g!PdSJ;opYHA#BMFY>dlxjF%f_6?VJ$v^|%gHI?+n|$hQ2Z%$U z5^~WpHKc+!f)?&7^@DLaX=*|=i@t)6TWldj5^}n8RqLosw~s19dHfoHF$Rh-xNttf zYH?a@vG{d5hJvkHrsSdt54L1Gr4cZ^!zlJ%@p$j@As)#aJi4Xpt1qbY;yE^A58$W# zmT19lmKVmUji&5zD)b9xHvThlO8US+DcVrNl-RGF9`+&U51EDj!wQ64I zsraIc0#G)+uiz-Y3On3^Jc6aB`yX=w&>GY0T!r>9WsLXK7{T@ zbO>_3%D_i5@jH-rxp%fY(rY?IKIMrvkp!1zkV=PH7R>zRGvX|Jpp9z#clT0paJu7Y zA4578HT-6B-j*QmgM~^ONhetRCcvkr0!DHDTF=x&l!}ya-47jj2v`m7e3BcBXf(f(RpK>AC^k}4(q!JV}qb1NV9pIGwr5L8!eV!r157*(w-IQNsUYY(|2u)0-+(gaZyoMgV1&wcYkf!NE3VBJ{}MbnlHW=)9Z<1 zlBe+Lh<=5QwaF>?X+_tmTPVwlSMi_J&8afmgq|>PPqv6C&osYQ3CS zViW~)Bl=N}33U{^)a7GSx35_ehO25$9-9_(zJqn`C%a7Kdp=XMsmYXy_|!IJFIu|# zsl9D*-$4|gOCsJw+2>Z`JXo@Hym{Q4VgvsMC~@?AwD)XHl}9zqWK6u^D2Pc3@zyd_E>N0fbieiiMopVzyj??Cp0xjv-;^4_)@y<-P(~BV?A6vj`i|- zY}{YqtJmrIKplJMdI|P1Q{}Yq+QO3r2s4ON_>62dX$l=mk$TZca=P;1xe0b?qm zkb}JW%tzQ`mF@+Lvk^JhLcP-uF_kD!?nvLkt@b>PXQE`o(p8m`bSV?ciMg@!QnJmo zu_!b19{tpu%sP92GM-;#YXlWm5Zbfzq(LePn;mNI^^^yDqWyrd*cwExQ zB1#CwtjNsP-70_djzRJmTMlEV`~i_*|LkEW=_BSBE4RcTXpSP0^r-~J7FGMPsKM3g z?iL~cvi(Y07da*xh#0{eC5q}H{<{!jP9fe5sgA18RSpaVpI7Q^mfscs{8Z#8>D&U&J#A z@79{~{^5>^9n0`09}mCRet=&1@kCEKAMRw$$&8o49jqgrX0B(+I6$XBp}?jxrBh8R zu6~|1TP5~_O;wG*G4R<~h5f4N5%Qcw_%8IrVImtgbEl5S_IcJazTOMtp1!k^M=qRa1FOv!b3CqFhtoniTQeR!G zZN}vkiuM*AllSU0S*Sb{iKcfaM!icDcbS_5WA{YXI8QpdqHyuTU~dEV@(%-bcnu<~ z;I4bx=sjY7Q}4-GwVQ}HYvJj?r&PzNk#D5%DAZHY&mDToex}-%SBV(&p<76~)x}}6 zePySdx=rz(W@k12Ydl|z7KD9HCBoicx?cEmc)w>+$^#aDrA9jWzPYEnH~DRCf22+L zZ)`6W2M{;LrA%m}G;!t+oRRa%d5j%c>iCD2)~5-fx8AEe}tN7QZRv zMkL+1Yy|7(hP2oVD0S(xrhTZ2E&Q;jC|*GAdv2(`bjw#k8REVQlh{=4 ztFqEH0gDHXMCCL-Zq$wRsmB2k1NM2lB9Hjhv5%!a29dUQv-1RtC`xitFY1CAKeYLy zx85_8=S8%`kyOIT0VmllBIJ~!G-P~uBOH6D+1IayiVc>yv5_r-P{zR)JQJ}0kEV$* z1-oSYOWZJtZ)L16ZLS&eXSh{{>n@F7ZC33+<(V*SvPq{_n^cH_$ng&*sEi_QYl+dm z0!#_4nU=bIt4*m`*_#A&-({K3rU};A->k>SG!+nMG|qx#*E=h?<=PAv z-y-XbCt9&m>#UwkU$4uelVFi4KZA#O?UEj_Z&NCfmLhj*TrnwIGw38ch%sAMMN`ua zNfOF;vXdpTN>QT~LweZy%E8o<(eE-IFiBz`2uhxd?(;TpNI|W zf=Qb4yyBR32dXzY7+T8W>J9l4`u9s{cwi4C|>C4hCLYjl+H zfb#Y~1FukERy^t(w(cg>WcYF2qg%kHGnDwRLzRmO0ZaWSUjFxP6oL+4|EoS10=(MB zi0p;&fH-vS(}5-ZF9x=M1%>}13jSpUq#G9w^n&#NPJH+u1q;IXC#r+#{)3T{w|}=H zdiFni{anJoy9GS^$E6Zs{&?@YeOU8mWu&rwWx__m@vE1G)kcj5V+o4Y0*wjyS4U;heFCW4#W*Q}?u--nb)2Q+%gI zK-@9W{c38Eu_0QUDcSc6)SkmA_Re((RO$2B`f$=?LF>&YfzugLQPIgOH}5x( zIW!fBhl-}~p7s3Az1n?O^xaG2oDQc{HVvNz5Y{qi$1U{v@Ev+UF9ZT{kj@%qqfsnq z?a~F;J&T>J`wnX7%PDvNraNK}=FkZRh^r#n2vPF*mAvPoZ}*x0rf$a1k+8ObUqjk6 z_MrFY%X@OT^2c=p(yv$GjA!I?3t9wX>js`(w<-#Z$3*|y4ee;m={J)Go`F)mK*|D? z?n25i?4L_s9eW@X&p_;RSlrlKe5aSLy3!h+_c>6ypWbpGA5U4ycvSk1G|wlnAD zp90K%d*2RNQgHrEUG?Gn8|#FeXcN%hh-aVkXV?8WO|i=ty6~9u81AW8{Pxx7)q}j3 zqP#m z_N?_E^K+{7tPV9juJZ7!a&LGmc9rL>j+5``2qa|jgbLr!chS9DO2TWqIR}WotXOC2 z{LKj-2Ig0Rmp5w||76GJ!J7T=uB+?waWEH`e|z=*W|_snuPgjT6>*#XP{5>ogSiw( zp`oc)t!I_%e#lR`-N(b?o(^D z@zTaiSIhO}RK826Ys!c@V5&G{Ch~K&w|W4!{r#o@1L7+j=AccQgn#mpuS5%Zbd8#) z>v5uu;>Ka$togkri@*ut90Wx&Dl#RdHyM-R_YKdbpQ59^V}JSqai2oxKFns_{AAi5 z_93LqI4iXI`9kS&E?i0Dd12eo0viWMQNWe^W*;*jo*o(4Gp^cU$+a#)5bZBEcUDJfa`BWA3}l#Drvd`6-sG&Z^Dwj1Mh z$-G_UU23^68RRJK0+Q%Bp2tr}tUG=^QrZT}>c2VM@+4XY{r#*HNg+FWcsuB?Ii{3Q z{Au$^Q*^e3uBE%s%17)`_I*|=myQlT)OlxYGW?bU6-(wLC~)2m4t!GJW4ubT2cOs$x#dwDBybLTRG!jZ4|*` z7`I!jPngf=cIU_Y*^om8c7uRJh=ygm5u;LNK>8b^D5q_#%Y@$!j94*0l;nn?3w&C6 zQ?j%OhqR&Mt0C7Un#}Kow zZ%&I(Vf>L?u>)M7*qY^>{Ms1zu1OY$)k!6l2#A@}1LE1|byo@x%H!AkeWXflK>K0J zI_TpAk5?6IDQg~1H2(O{X)vR{$Wbc`md{21G@%OMWCD++;w@2@9$!hcd(@u=e9z}+ zbSt;TqUwYe(Q&&=@e6S=t7iGO0CNX<>>Z^6fC2w9C z!+iO(Poax@)F8c*&M)ykXe?VE?XvIUh=mdSPSgde;0lZZ$7BmZHK`!HjC3VxQtL04 zpU_BOqs!cy8Wl&vV=h@5dgO9om_N=g4kG{zMEaOYq_pnTEj*o4R8?i=cIx`ja4-q% zz?cI|!p`)XH)uM?>3ALIK3kdwxeENiSr~)hwGleQFvk0|$re7M5zwe=k=lxF!Te4a zw_IlgMA|FHKE_()?E0_f)yJNZqh_C;2z}xCwd<_Z64o&JMKkDOjjZ*+S4BaR&5EK^ zIWNlIMi)2Rcx()ecCWIP?CMj)poLp{SFTIQ4KHM$CQOp|<^xN=1?Kd|$Fd~K{2{Yt(owE2V$W~x{7#MRzy1GBK zZ=8?uzWMx#P(&NIFAe%!jt{Upu) z*~`kTZU0C$?+v^1-6Kga@rHiwp~o-@x!a#~N?*}Xd=>hgbTBztx>-cg%*~S=lb8+$ z(#B<%!`!yX*}syJ@}LydRi6yzepnhleQn?0a=ZJWgS)HTp5h}Me)vE_?h<$2i@G>| zN6kouHl_OuhWzb`PHt^-0_~l>sG%zLr#2sQ$L|VqJ-@)Dt(=9DUQPMkYYQ+dBQs2 zZ6(h^&2ICQO1^oR6^3`2rN`Tn2n@04A6e3&jY)+reH)j^P{*)++z>ViE*1Pbp>^cv zgTVn0x&yPf)M&HXSbQIwf|4>Ym|$Wd|5av5@mBFw`!%`|PCwJl@4VBlIOdM5^MfGn zM2=ozXEr^98#zHD85z$Gv`{jb(sD7k`_nhnSTC(NFZzN9+jYLL`blu-zrHey z!t3x^T-Rs4rgMXzJTE>KH?qJ~eteB{uM(VQ-Yuo@%$UlQ6@MKh`rcV9-cng208~1_Ab!b$EPR*eiu#|VvJ`^ujgfZfLM6!Od z8tHi%6g$f_Hf9zV_kn!9`^rV$5SCMEm0UG_q$fxDs&&u~ed0R*i^JsTu3#sqkiIj*5?+(|NLd_`DW5YUu4syJQ^? z^IVN!kW8mngDan2J#;mGb#+DHH9FS5I$X{3h0EhfXkVGxI)l%k3PDJq#SIg<$=s9t zah&L}0>Kk(sKonQ?y%pT_2f#ik&LE+BP}4?fETFB%~4>pzjGe@`eSAEsXS)P9dP^{ zocck>pFuQYCD>0Nt{2>(W3mU?CqEg!iZnV<$?fRuHC%AR(k2TcBJOc^UfT}DE11j2 zQyg)>)oTv_ZP;{`r)&Y7#wz}9sJL4U>F5)x+Le8^*M&b&cc2cBokIOglYGpt1SLBX zuV8+CLx1J28-z~P)p(TP4Eu!>eL$;Bfv>{$*4EjI?wQ7kH5v@C+E+uW>SwG9!mH@w z-M*Wblp$vW52Wa@W%Ou2TGeyf?iOpoU9&dYL#eHy&lEjUvVg|vN{s$tt%&*&MBGhBr!*B0;^V9mMY z*xr|aj9Zd_Zmg)o5nm`Y8Loaj_|mZQ`^steI`@kc`-(La*}A}R69>*oule7UZPt^H zwAv;~>5+5h_ihO`oQ9q7w9Y-3V~>vKwb-*>OBAxHk4$Aw<)>%Z-x}M85hq>}Nx1eN zMtf#0suay64;)tMk+=cZQAqSJCNcU{0UWB76VGVM7^$=D^QtE*7{nAl zoy{igC!E5MC2eZXh7i&Px^XjV%6WB9s}^V8HMnQcGE`BX3Mcmee#|Fjde1~n4)c>H z&ztQ6MyUBmj20p~DSMwsj~-$#To%lI$r<)!cY&UdW6C&iQx5yR)Fgt4O1N!u(nC*| zBkX!kUPD8KWc$X}y{ry3U@Xr+m3U_wi?9E0}v9ik+ zOMM`G%`J%g4h8V!Xxsb0Uawc4K{?Ir=*Y%Xke}tJNgoAEi4_Ext7;Ybz>a=gVa!J0=2x)O@xGuYI+l! z>0h|JH`3FD(eW$?iSzNHD|xgx-Z3OnH=_khf89Gt@P7QjV(<;BQU(8j&}HKgZ_$_o z;LFuJeem@OTT~nJu*L{)KY^}JYDrgEX_e%oB2t8IFE*&WTcN0VD1%c{mu0l`(e3K^ z8pX2eyZ2>gqxXnnia2y^8z-P-`wtBw*x$YDt4zib4sa)QXKOr2zlIslL8-z?vkT z0wtyAux=UiAW@o8NiOd=rV{z;Tf7%?6Qr1#Jyaj6I+~rb`y`Zd%2l*~ zZtdFm#IrzE#Pb&H*q1?og47L%NS@iQIUz#Ub(ilDpPk^8`{gZabXIU+mGtGxvTWQT zV#!oy5L|Y&X{n{}??d)m49@yRT)ReS2Rhx?<6TzZm&s1tYU7`+go38^j_0^mk+Lv- z^;0flUI+Q2HVO*i6ja@gy?xPSu(x|*3oByKM?XL9Ym~JG+1t8Z#xk#+r2mXgcMfm79m3a(i^LDfidA@Bqd#Xs?RlZ*@`kPX zG{V+gaeskAYSkv6+`jxam`OqR%}(`zB-WaH1Ddmh`X0Ob1xg6RQ56lAsx#98bW13u>**mbe2<%gy)}h?tP6M~dRZL_FS_9C{ODUO zwb&<5)xuB;NZa&CwMUEyyOlC9(`Ab59M^v~ztP^=IZACrZ0lex#-;lPNsq-{n4l+I zCXMqZnuzS{batf{Yu#EF>?!8W@DMN0-txL*e>)eQK5cdq9}B8`ndDBjPJBFV)*9ec zxOEq4vCAe^@F;WL>}?$fR$(CRO~SMv>oQF)JRWU?9Cqu<6~~eV>6r@jQ_YW>{L{Sx z+zgqH#-UFT{I(G$WF|VA=obS7)uUbz+G;wjZ$B7 zyLEPaCDfa>{?ztln%r}oNk2dEcT)7b9m) zNHj_pKu}09yXiI-o=A0<0x!Zjc9Q^K;Oq6B}tfc zW$yQH%a__J&pSO!7?g+$6tQ`(j+fgUv+kRYRqN(7`B&)9?`m5m)>2_*klCfrLrlLE zh{}pZ+?c5BDtM@_7Ya|{PcS`H>An>ixp+t%>`k>rs^NgFZTl8H5-e3;;6_>pXVf_s z#-}7E`C?3Zx^%_5`V{G6WhDNJKPJBjuP;LJ*H;j`Q zE6?6l@hK~{v~^9oEq5~g{V&O#wS2W#Yqj_uz`xdB9pm??Wh@}gL}d8t6%Bdz8vbq% z3mmqzO7?YQ&=0L6A@06mv))i!C`F&TaJ82^vc7U=BG?;sawLFsG?$AyK0J&lKx7s1 z3h1qO1c&K8;pv|s^h;cMl3vpL;QFPYZ&zFF`Azu`Pp$`Cf`Z~O?14$Wo+7hJEO`?3 zHe&&!0=E9Lj;@a3pbA~rIxMC@PWwhCDQ;vD6^8kHu3Sy4bQu8%v zwb(v&QjBp}Tp->RQ4b1qR~h>*T-ff!zGc;mjp8-ceh<$yrHA%;;L3kE9$9kT$YRX}Fp)rdTUOSNYky;4OA`*_!~er!hF(K@I}_RapJv+F`xO_!6?xgHo99h{Iw z+)Hn60I|&yp=tpKKrc;k=`!!y4G>mn@&?UQcwEoZ6=MpV$1M}Ob3J;gLdis-QPSj# zKorL=mtWv6I2F51T%LkO7e>Z+lRcRt+r7a^(`K`~6Q%xihm-+8UP5A6!f{yf$WDY} z6j<@hyu2q|(#fr)Xt~oTQvwL9=oE+rcZDhiE>BNu`0>0No4B?4O?s6eCUoXdE+v$# zQ&TMnbQySX_umeOBooi#Dqd#f zc4yJCr)_Ue_tCAyZPSHk0|iK(lwt`YI)GbTad)KO zXvS?YssnyjDusDpXef|NuB8*?E!itgD&Ku5r?zrC)mN`=-F0)Q%>CNZPQ0A}=o_rM z;ha(C{aU$Squg$VFpz-AYm9My{fdcYN7Ah@&}tW;;VY-MdfOWkS#3Pyx7?bS_2n@0 z8&AxfFo2yfu#~dQ-^MC_l!D1VAe$y0&%$&Yi}K5DSKm%y`NbbQWO@!rtU_F839?;3&IwXr^8 zvgqksukS#ju7pW*tcn)9bv%;tHUKA40MH`#HH~1gWWGBA7PTVL7IrP-JG#?D`f~a> zdslN3;fdVVcl!oFo2swbK#13xM)N6QG3dkJxW z*s`3QzO;9&VZefdxD2KBqs5906S{77LvfFx^A{ID7g6r3S`=6T!g^v(OJiX(%(zlw z{?!ciWSi5bE|DQezhY9=Lt)1=YI!tG1ybna9K29PDcV(coFjmsM00%V!l7Y*iJ1LX zj)W2p3dDTeNkrrom_$~c)?_*Xv_0Gtdzl{V$iPx^#G$5co$(sd&{FD?0k$>a5v>yT z4&|<+y7J)Gs1_ZT+09jND_9CE6jK5~dQ!cbe0g@Jwbkca16&7{(+_#0S) zqU6$a8~sR0gf}plUYgz7iP|IVS=8wSW;YpxCd-KDtsDx0B-mFvOt~#ZTRZDUF?%E8 z!Z;i=p1_^MBCJ@THqnAv023-8D<-Kh7@zl>Mx$%#xbVJOr!_s)X0}@GjzFdv6M+Sn z4cm+D8bzwFhyx)r{>ZwCikdI2wuQL$p05TwGm|fQt`d9>$mP`GKP@$#{*s@_0}dZz#oUq2JX_HSgxLA%08>Bd`rYfMylESi&nmbcYhx#3OO>xhbrE5f?;%ms z{3Qbmt?XpElcjFdJlE>k?=IuRc6pXI8KR=F<%>c4<48Jy$nq+Lgvb*2R zBlUy>=p^FGBQWR)I{>D%%3{Rf@90pk$@rkr>(Z$K+or@>>5T@4H5~~@a8aSV03+MXdr|jzOso_YIAeDL0Lzau0$IF)j&J`Q#t;>iXh=w6Kflb(9QSUN`U*uTuJju%ezrQ=}Kn}jHUU(hdP3L_66Dhz3PT0{?$D1M) z3)*0B8^HmK<&uqCTSf_+jsWDsQN4Csa`u$Tx+i;=5<)i;e<@Nzv*Rq)swSdP;ATs)DxLWZbs61`QzxqV+NggGzKDm*_s+i zA-7x#4*tzH+Dig%8KZHOaz8@K+G4y~PI}YYr=ff{JIv zmaW6|Sd=uPqRC;K`Qavygm5PIvqszSd-<3vB9LmYt=B1(IgVl7^2}i-pFG14_bN$( z#e6*1a4o7*jLJMu+E(3}D~dQDbGTc<#zYJh{=L{X)9vI?70lXhkqO+*n;{F@LZV?V zSIM?ySN5-sr2qVhs=+I(nK@)u*N)hwp3EAX5NSL{u^g|vZ+e)bP)ga^pc#>TTyIuche*nwV=l>#& zovt~xMD2D}iw*I$v9Ga@D3Q3Rp5tq;eKU3GL!Q_vW9esR%_^B2p8Ib0*whtGUT^ud zo;k~5o4XgeL;qN;7`EIK=_%#o;W$Gt=bVw+Y4Yv0(0zM58SMCK*;K7X0jH57g5|#3 zD(3Lymw|*D0pSDtlla2x9U=9;D!meHJOV#qFu`w!M7O_HU3!V~!EB&1(Q-vihd^2w;40OP|ZTDkZ<)H^ht41oXZypO0`K zj!%J^$oExdpwwR9K6e3P#vI=GXTB#w+by<|ZyF8v2jrLXxgYtqp<&Dd&QI=HsTmh0 z-Mj64sSKLPS7#ibpZ{VehL^+8uFaO>BME)Be5< z+I{NqUnq*7D7ew5`t?H>4hAeMe+Jl4DwgsywEc3ioXHZ36vz=Ror8_Vs~1m+Hg8p$!+pT$A((#jdEd!_e9ZzbM>_% z^twrT^8`Z{vC@nT-Iojfn3#62`01&k@lwwgyS9);SB+=T2q0G!oZ4ehsN$$~%67+X zQ2KO!+ZlJ8Xy^Jvnch`uLy9l%||t`9{jz{nQyr2y8jx@&{0O4<_^r$ zux;6OPWj*o3X1)vd3kdmgkJb50UAqQ|7e&0HLi+8sy&opFyDPQ{U(hvZzl^9I z*kgOow}k^rEh6YgA-88X-voOPZ5PU?Kh@fUyEMl#=RwCu*9{v00o#`wPozdlZQm{Z zNb|H2MHr=`Chf*6Dcf?U)<;VfHiQYwRr4Op+agozhb@ol!qqx!jAsFA(Zb;+(pF>4 z;>;ITlRPkv*ww!xRN=n6&0H9{@6I*ITNwK6VDp3;>fFDPF-qhk1I3h^wIINGbQJ0x z;JQ4PxOgntBPz(x=y7~pgX(yFIy^R&m<}W1^;&fDGz{Z1pHh{K$ZWE9=|??nin2%? z0f@gm8nu1bqq-uTh&0%)TNZ!S=jOz z^+>{@2DWKrN(6EPvCn&uL6dj5w~&*@YpKt>WyRhL6;33}}=ZeeQ=XL{awT|`G( ziOcHR$V@5`TjbnaMK01fe?3R#nrOJvMj`JbCi`0yBCbfkB41H9tfOBXb5DI}cwfs? z;E?TtMt!oZ?T_hX+5O<<2U?x#mA`3#gT;Wh4+>ye4e=!*H2RJU=BB6L7hZP`V?WX| zL<%8$J``fgMJ!Qz^EeEU?LLS0dEUWtV@ui|ca&^Qn|WF!iy&jVXWp;8BlNI`@VUnA z53q2jFyS>~ef&@r=OU_KBwoU6?n?I)A$~NAG*<6b3*;S=a9X+YiulA~J{bYlL zTOq3QzXmWOyyvc2*a=^rPLrPA7P7zC5YcaSE%j~JV~89J4$GyPCTuJ`2Jwo(T{6RG z=~DIM6<;e;E&;a!5>M7+NcMrILJg&q{(5eDz;(DYx<-;Am0r-%a>;wnoU1AmOi4-V zI5BLGbR$ldoe%Za-P(;$H;72ko5#NWDprb6G@^#_B5M=7rQ{J$lWK+dWh@r4>;LXF zFA$lH)MVO_>QBT(d?6XSJ*B%GO@~$3`BU*`Xb7?!ohFH4+c}y%C)~E(ZW!D~hHQ|u z@M1_Og=X{&ln09)BvO)NA`(>e#vHNE!@XD(E!J|b#`6@@D>>D1Mot=ZC-qis_#+5f@0kXM^@3o%0&+gizt1UtNIG$`fHeB^q14Ha+Z4c3f!cEd2%ES_dD? z)=l7H)EIBJdYq-SOxd)g+~e3A%LY~9E2++ROb}(%Kp`yM6xa5SLcCi?=t1fS;L@~M zR^PnTZC8k``1=L8gsdi|!3{1tj((GS36oSNpCkR=lE7{jr|7wly23| z!=V?S5b1bUZZy^1hB8GW^iwfK1NK$Fl|kl!&d=o*(!{Meag)@Vs5q8SK5wV@L|fSv z3kI5U*|^B7ju*a6OERt;avAf~h^%Q81~(+!qJ|Wp$?EwF&V-7fLwOb=d6BgfmB@co zI(i7&4~D2^WcDqV@nR~n@rO#Pp{j$;&j|GpVi&AlT>QGR!B8%D zNHTh&5jZyYlA=Jl;1FwVcX6|^h5$6in>=?W9BV|!9YVnd>4vIhB~W0iihNHr&;_|k?aC@ln_5F6`H(W0DEBPTW0*d{BWv>F;hafrIK2* zE%#m9?h!bDAgvAfSaFim(`0TMReylUs1EU_npk$bM@;xe`aF6To2w9KL>J-1${C~; z?hnt{ks6MrX#a7yBlGP3NM$nk^4&hHtJ4Np&fh347%*5? z9P@j|&(1i{h*X5bLoCN-+ZH#+t4|Bl7;`aY_wlS9KBwzf1iC-nEq9}0;8jv-Bvh<6 ztQ2pz*50xnAY$6(Vpc^kNO{8*&|XG9TrW_Uf(&-ghbmx}k%k16Dn2*a_;b1K4wy^L zn)cooQ4U6C<7&H?`~4PMa4IhgjK#SFl5kKs-lV`pHP6rERIVaVZ9ivDi%zm;bzM1I zqhbZe@~;_{l71^Cvd-SM$lM9dawowCcjM&aAeB37Lv8umUw;AE-#ECmQ6X3ae^cW| z%;Uim^K9tBMbXz`3$-!YAM(tj#&{~lBf@L$k8ig{aW(40A#<{GJS{xP#G1K!JaIgu z;)al^_7C($-B$pj7?!*>V(*KU%E@iq7tAO28bEq?g?t^Lm`SDMF9_nTr(#ILUe|ooSL{o;SLE~T zt0?Vw&J0`NJ)+Y%H~uLm!#t)Bw!Rf5gx;gpc`62u<9o)wtWa-%*Q4Q*r7c|=#BNG; zlfKg7rVmF7knw#4KcSGx0PuxSQn9cka5tZq-5s0foy&G>R`*F2fj}%eEj_Ptm4tL$ zW}`)O>9DhsCOBV8cth^XdNWvudmEm>4Gz72b^j^)K;>%Oo-0o5i}~iBQVtQi1~mH! zA!TAd8?YUs{aZxK1+ddpAC;^&Bw`vu99HtIY0w=CQ{OS$(eEVWD6pXQllWj!0W}6o z8lPe!2c(zN8~^iG!YmncVkh*x9tYJg;V%`oIKa(+Dxr=0dKy`d9KSKtQ((zs{Vn%_ z8vylM#}jC_B4kicMT_n~3f6NGxm2(zS+FlEx_=nG9O@k!Lq0^Dp{vtQfyFKz0%js* zMlTsU^`ymOP>@c2K-b6k^7(%y_eBnS`Ts?ZJjA;b4H-EQDmNP~3?S0yuEDxcFd%oq zpC;611XUw?lL`Alg&J@?|7r~w=Ro}YQ;Ul~aadKPlb3+aG){pmT)6+`%2 zTh(q5`yOtRssMZc%Qi4Q0@=L~Z=)x2kclt7!RMxYArX-jZOsl&P@|On%i{I1`YnqblipTodCQ-FFD#)sW&91QYaEJ8WT)msOD?)KeoTiCEJx$E zkFG)R-Ab!j#Y2SAHI|mhSVxd$W7BNmBTsXrXEEM(A!26_?IfsC9w6 zQMiPHyLY#0oYu_U_7=?o^mzsWEHVEf`i9dxaGJVZC40W8neL!wd%it$o~WywmEcg~elq2G-_euG z$-&DI-1HD{QnD1T0taWj;5RPfgGmbUytL2=!F+H{y!7S3)$OgGH}jyoe{l0+#lL)# zHtiPe;zD1{D!a#$Z_C-<+sn6*=v)_PJFZ{)qT6Wx@D$!1*Q*_$#9xl6_l+bnRrcjF zcSQ>vjE_QJ(wTpJm)q0bDVXhGl%j$U{Aj{kH1HCjpINrSuf=4wUkGaK?sYi+X?{2a zuR*(a!#~X$g*m@&brJ{=u^%c#x<#t*4uFq1kSJzOzD-AWUEDGPxT`9%?%C?+^VbJH zNgh)!?D?K}aNE>Bvj+$2@!;x7`!|C_krd$zD=jQIu>qHp8eC8|o3Pz3yY2R`6kWhO zPnUjh1>5TDf~+CLsafq&*g0LT6N|02!H!a2aum3Fw5>CH-Z2%sSGbyQn-(p5Ex(`l z@n*^gdbD>45(}Rz&syWE(C1sLoI&Mt*Ce2Cyr6$?#p5fwi|V6qFlk7E2MbnK+#A9d z-M9Q1A3b|&FNf_xr+uThKayxCV;*SGr)yJMddi7Lp%lYd}8eB%x;}dAmL8lZ_Q5;_v+B-WRcrJVwMuc{UI3Vp~>uhl7+R1m6BFgxCyps zhGz}y;Wr|w`lNh~6_sq2%%#%Q4>opYz8{^AKJCJ7?N-NdMwqd}4Ky(liEfq)Urr(d zzl9)`y1BJ?O)CyG+w(a$wF*#LZ{walu>9F$0uIvX$_$)}JD~oLtYl{=@|YBoREbwU zW|P^W`yh!QY3Ezd298py-zK?)9lI*`&R|^!lKn^*>-Z#d>uDCa>bPcGTOEIJ9F`FC z@sj)1?GfMxuy!u{lB?i<80!J?YW#7WNN1wKlP9xMxp*4C)5Z$L6T_dLJ>qkx(5W6ROwJq>5}dckOo2N z4gqO~PALHa0qI5%X$GWo&K`Z<=e*~~`SpGC3x<8~d+oi}+I!_im-Us>mr{J7U=>u+ z()N1adoAj;eOi7Y;W&^PXTK#>)_z7WY8>{{7~z`(pW1vtZ}ie6JJQf0i>T$nnUNW$ zqlH0qha(-^A2#yF`)ND;TeceOyCJp2(I{)7$EH4$DYJWLET-Oh2Z$<3_yy%OB_b=#oIQ?%+E}V9LKj*?JG4szPEwA$q|@D&^0GgcE%VD1wFP zcVBgUpR&*l9c54ARDSkGNYI6+{fydUjTupa{gt9;{b)?>>5($2Uu+J-HU|{8b3IW^ z%{tht3K`oB-B4ch#oC28$mcNRSG^ip*y{tg2(Ev|Q#VXUYAd27h;a7=^wZ(plv(A! z&Wt^X8O4QLESfGpnvDzz$_BRRUL3f$m6n@EA^Sn1ZE`1!1=<7W<3mw2 zC2Gw}%C^d`GZK{!(Rvh%3pyQYJ!;uIyK-wso#vX9Mb-H6CD5!ySyoYyFe&XG^gbJBB8(2JWjd8IEj9$tTb)sH>J z`*e--eE`CIy1IDnqE7Oa>*VSJ*icX;{ZjL^$9EO^%qObB&Nl_z@ht6G%2fjIS0kr2 z@tW{fj<#%dsi;zS8jBVUA_R3q`UMvh8Agx`LhJcQnqB6K8rD%R7WUE4ybn3G2+vZkOv6Uqp#l zet0Q~4FQND=r}V{iN`yO`%!gBNq+#n` zRlUgT1zlC+$Dl{uiJ|Zmm^~M(pM)Nde4;8Y35d=yJuAW94me?Qx>UN_s&|b8SbQJ> z*-fciWphzK7ajCUtrlQ+UE9*PQB5c;K&d`}&2>wIg9qd|rSZzOZ511eH2#iwY^<7) zU9h1V3iT*=>!t8-*ChX&rKJlWtG>rLuqyil)EKR=tApq<30KBP)q2t=A2H4a$h)${ za`9N5FuOmVpRJ3627x-J=Q8vKOR>B-sNC}Z4Cw})H6=oVFFJB5Zv)DC$foO+^=fnn zVe3aZ*}3vn9jjN;NYCM#Rg~O+lC{!JT)dz;grPa`P zjsLTX3PoEhP~6V_0>(;|L?3OWmJ$22+|^yZQ0Nn>X96C^l$8Z7&yCRnwsVWB?hb{_ z(<-1zOQb+w)Y)H5Nm9{k?zpL;npA2Lzm>Ov3+M04i~Git#qw-@2*1iiG7z0Uw;;qkA^jW_5IY!podsj+$G36n5_H9j z0~WFsC;?MJ9{R=Y;RC!d-KsmPTi$`tx1<_*`Vz>QREBw^$r!7b8Y7iy4|*pK+_0TP(sx2b{QC$$LB${i`Mwh=#Eugt>wx%E0ua4y_GpK1 zrc>I3$m}>rLWWU!0U>K8?4c! zo3c1}#Qx;|eb}2Y_Up=CC@cr}1~F6k&WOm_EPm5WeUQ8ywK6td}zI;YWd@XMDNOB#05i6kb7pEx)K*+cn+A*sg zkP=?AH&@d(3&skhW?d6GzweWMsy8}c51&S|E z=~H}q8AmC6O$5h7GAlyFgm+`Xep%PV&c4s*I5OyufCTSuMFuGJpz z*eJJ^+t)5rM~Mwv?6-tyZ3=QhjuU27mlkF=M}tJg!{2W&mn- z=N4mwx0j==o{QX!6Y+q)pJ>x6y-+!u(wc<3mI^t~QlU{3UqWaa7iXxJ9kzNrYnrUR z^LL!ZI2)EUr&cbynXNB zuT{?jzGJtVpi`CdId2-(|C?a7gskgUsB1Wg3g~bZE?=^(vp}il?9$7kt&m*^qLWGT z)46ssxrh;u--ZOqhA!>hZi!Ad$*M0dX z3j%pfMb&B3f4O$JLEtg{To1-cj5qQyp=Vz7D?0sjAJ$>jYFkuR%M{*zc)(MIGRZ$`45Lu&E{7?Zy(Y5* zV7C2B0n)`I7?+lk)rvMwFFA1Zj`&p&wSquiU!z+7kf|w9_1mRlzEl~9z&L&D%EGos zAN9&5fw+mu9BQ#&ZxS+xv`$+k2`au zVHDye)AY|D68H!akz)n;ia^5A>2HCf4}cl2)K6#C4lB*nCpGZ$*)1k`FUIL89Llm{~P?m z1naiW5TF^@Jj?FBj8Zpb^M(`X$jnQ zezgHC51cYpX^w3Q%v%5*!4TQcOjshds+c1a{LTX9<-0~CUh6X(N~`G4Y@ zfNU!&tazX!Bc%QSEcVrIRzn|v-H`v2X~e9`iptN58VcqC5oPd!tC6q@*QmBE1uPtV zJ^Cl=3TV`m|NIj8RTi*64XAT+9KdA%6W#>Rkaf!dqvW6Evp|XZfOmsSP6}oPu}%=4 z1497xT}D2QG^(8jdO)2&{^xD*&udIz=7;hu!@vr}oONKJFDyq9rpcqnSj6)_@(*AB z0XEX0-4e{WuHeNwn1-VpN{J!&@AwyPGSN2wJ(5lbhUvMVsx}|b`S0(OFkrXWDDj&~ z_3v4vxNV;S7#z&`3f{^Bg<+P1p%#4L(WA1r0Rr^Lva&Mu|A#bnOq(xgL@1#4KR<_p zeq%yR_zh69 zOb+0&$|sm7Dv;AZ%jk$+!~&uA>_)ud<%tEW>+5ls$C{K6CGkj@1O8oJ0}Q2r_489){K^mauU zOx#OScs=vpwfs_;*=kgz5%8h{y83w1sTq)m(z>NZTvzVdN5DdP~o5 zM~C(gh&#`mV;kWdUi|86wU3EGEM|%j4m37OxJtNtdqu!@YTo?if)3d&J`h?r_+g~B zlZXR+1)fa8k^4ALqzi>IE(*3RZ+#@W?7dw2g+JrCLDcFsYpF5fkl}{+qwY#O?={^e z;hz5APhCbxvHB?z4ET{>?5{IEh}_5>95+1>2EV0>x`c0nto48Jiz-P88;@#WlcD^k*9MAsI}g9Pfmobb(Vt+~JJ;*Y^yoSYu{7iD1(m5? z+Mb4H4(@{NOo}@hKn&hMdK}$OPMaYWPeaHYJsv`(X&rs=Tosn+%B=Bb5-)8Y+d7rR z+m%(Uq>pexIg$SG>88PZ75c$g!@gXKn;tHJn+M%yAX&YyDv+o}t|nTB#+T3F zmBRj4+%^BS%_B`U6Ut3W2njA!7{!v$8_xp*?SSz(W;x12_1nU{v{-wbK;p(2&8;(q z2r3K7TO5CL4Fx7FIk#@E5Ob zuy930#_xBr;C#_znNRkoqTZeB%lJ|DxJ{uN_;0=(Sw>20GtHzxIH5`O8Kv2p``0{{ zms@YhfZb>0v=4EQaJTuF^lvm=PFrMy4^05+vhSs4242@~4jI!gZg7mRcK(-XufhF}n1g@qVvoQkRw6IV4}sTs~R2qNovV0nCvFJ&ir>nbUIMCVaRu8c{Ajk706 ze^25pa_ukGUZ1b$yfEZ17fOn*7J!zM=^RQJ2WFVT7Cq!CC`z>&*P|7r1)z`&vO!p- z@uD5&sQO^nV7hkX-Ajf*&s|zH*MiCYMXqM#l4glc8ZRfE%kq*#U{_aJwyQsBDZLRz zdK4<&OY)E{m?|jL=i{+$FkYrz*P(KnD4*P)gBcM-8?lD*M^=ZT z?Fxj-QG8aEHkXCTDkt9jJlv96YZ0Ftg|nB51fw1;!@a1T@IHfgn2#s^9;|QYYgXc zC?C29;yO`Y?(S}yR6X1-IabIJoIG6Jn>+i57vD}txBR*KhrKdzQl+r}V{;(fFN6;mXl%J~&y#Pb82hhoymw>UY1=mazVxa;HL=;Ipo71& z@LK0iju}BkNO!h^Q7IYeA88SQe)tYUsHgX;Qu0dj{(^Rh2Uu|r6%U-VDDU!5K9F58 z@xh7k1%ix`J7GCCHBYT(!3b$~J-WMwH9NJV^y|Y>T{FJN9(_csUN2Fxg)k~_FG89d z<3IkDM;uGKRY*VbkWv3EnABrqmw91HL!nEin`e~G-yVAS z#CSLEoP8mXEU#CkJf37c7v|7AMINuzZbe?}m!uXW;?q8lsN9Jfr{#a`DvDq0sx)*w zS%IPmX}UTlX@};17#e57W>-(tZbkj^FB8WVqICe4wcpjXUux%=!(UCN?~6MWCUZJzX3*c-e#vI_k5 zsqplbDozl<^_O;FTY`&Zpjp77m9dEghtPMl(H`||qP~#D3ysl&PcQt8z|lZ`EHv-< zu)?7VR( zhV$Y@88$nay*bpJO4`NvmeOofRZkh2+(Y8!kW4XkfWo)ma{Vod=Ry;EUjKIdeD|Cf zN~|AS+MM}I=WA5qTS^i-zpHn?NpT!}OA@=gHd<(kY-`aP=#8DWX_C-sdF7xx;x@0S zM=sa;8c~e+iI4a*4vjUaovgl|sIbR#>pwJDo(7i1G8s+1%mfvxw;^gR=71t{gEQnca=~C9Ujk23?(Q2|_u@!!| zYVt7j?3t00&*hLw%Sh2NYOL6!z8d{F3Oy2FRL2tX;p1;q*GkG9R>sT1r7V%7NLwZ? zgyW-its={6itPz}lbEUNE}SG%yR(jF$|BU4Z>2o%8f=-iF50q>6Ef;o97i#FO6bH~ zP31RYiYL0Ny9)m2$u0UsOWm3a3VB2qd+4(KRf||MHiftu{+jV+iC85el}DUYUv@HS zj>OZc7a1kS(3!VLa-`%4PYTwbjBc84dUby2X%x9ih1SpviTK6auvsxcKcCI7Q~P#U z43aYWj%zz@_A=Bs>y3jQ+V=Z%ormjjoERbg-f;Bcdh#1Zpe6fjtr*eFMOU&sLF!7-w!5C-jvo3ug@!dmALzH;MC+&;yem~77mKWE-kt_wO|EZ`QY}3 zG}g{!d?dk6-1PAL#rF>SVTI?PePxPmC||Fsl^L$Zq}j(yB}GgOxh&NO?c4Z>=kB2O zZrFDl6#2&aM3Fe^Uhy4U8%db&!W|JseMrCXm{&e*cF%u7xb`kgaEN^+*|%L~4KjEy zYK4%5{2f8R(Y*Uv-igAqs$mgGW&iuyV2m=a`gx&l@*r}IE6$dM2(+%!Z95V}tZVKj z`E`b^OKRx{E9UHT9WEAm;|?NcW^ZLY>RTM)MK$<$KPk!H5lYO+mi;9oE&)W26@{1{pbl;Gh$hb# zBJ46B61I`hLW?T!4SxU9m>YOaXK34IvV5m0Jl{im`4aO+>-u{&S#pT4x5}{$oAHHX zET^vGLd-aiI^~^@X-Y4i46(wOtHbO{)8b8U;`Te&gXc!C`{doysdoIZ2%h6`nQLA^ zdQ$8#tBgYIydMn+S`WrrdHpoU4Gz}dDQfb0e2;yVB;wi@z)xHm2(4Y!)Y4*}KHw|8 zi9r1Hzy6D7*@ZMxVL>I&6e5iN-h_L4Sa-U5*@U{Gh{$P2f}M*gF=%)x=f4S1@fg`P ze+HM=uEk`PTcx)l5TZB0nHCK&e}$77&Ql)WfjAaTm~Cd)k`AOIcN{uHf*nV^v5Zn( z+w)F17VJ#ig&DD>yTU@;_+|KX3|eH~!Qm?sVKk#J9iYZX3w|o?5W628I8BaiX&(X* zKRsVvB=t`c$dtQJBJzwNwL8XHh8j*fqaoT~Ra@ckEeQsI*7myyKbXgP3fK3JCEn{3 z5o`|9Fhvh#!e;y5VLI;jVm125eFwA@ z=d;jZ9?1LB1nHC#j*(JZpXA7pZk?B1)u~DYOidMD&^hZq(mX8)mrr@6S^a=?{5o$} zji+rxn*u^NHDZH~z=d!w5sBb}Jb6FixY=_aD2`za^giO7Ft_VvV@p4sZ?{Xz1FVeK z#odC+I}b1Sr=-q2r?Urcx*~qQ7q2vbxJm6PJ`d^8&kj^chp3%=6#Av;9B+HB#gXc| z*43hR9;_%zvoJ+A&dBFVK-z%o(`ilt;bk(Qb1*WI{Kk8XeDzIP3s;>mMBhCm87r1^ z)-x*uD_9=S*vD5zB_3jDUk@J_*glHY_ok=-avy_zHmAi9q7R)sThDv}uGqljH=X@{ zR42)}kk8jyG^s;-I@Zqpwr4#UqEh7~SV`+#(;_v&W z+vivp*cB4G*YG5N)0&nnU-E(lD&I8s^E~t2`I>>zKsn#YWm_nr<0&?z?fn<=xrBL| zd$@u=uSY}N9H=?;nB7F(Ru*ijwBavP+OJ3|(lL4Rq@stj?+>)Sltzj`=7B7W!XS2` z30%S=;{q=h6rZyWRaw?h4BMW#W3F{ws}2h`{yFV8EZ=u=eF&Gr=3BK7k=~v4KZ9CQ zk{q{ZP7vz5%r_yODJ+E$EQmF=+*?hdU*qz)3dsF+H@7=htHCeMYn9dz9kjZYhnLDd z*z`=)?~soKW}T{zKDrz)xom)2EY@bWp7Yi@@0YT4qhCpyF#5%PTu5a+?PLAvgrG0t z@qEIPeal)bHGiRS+ez&;0)nQGDYp literal 28917 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 z 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 BXBPkf delta 27035 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%0Dy*NNn5>LWZtX^$N>q%jZ8JAbewPye&^nnEY0i zQnxK~<0)X$bV3u!2$Ft`8OeM;u(yqZHgxe#!q-q!6!$6dar@}A3fw_eX7kRU?32;R zX3Rmt+EsIUb_is|Q;>aNCAhi(yuh^8?yg#~+KG`nN-V&N9DI2%@yfKw>@$})17|=l zf_+}z3r%Sc!}9a|o4wi_N@0x1VE79M z(MR$#VZ@5#V;sY&YJ2CN9t>I>n5@8ITm6%OmbHiXqvcn@GcZocAiKyhj~bDUidUng zv^(lF;IKG}KSc!N;HfHH&$oKynWGD?FA~=lvRIXG-doChIB^X|iqi~G@QT#bR1}?Y zJ|F}IKC|brm0BSMJO|?hZ)FT#=D4us3$ZiZom}6Im1mCMpV~JI^FGThO4R9&!uV3| zcRa0W*=Ny~^qpDrho+|Yo!I{Fd7BxPlK!;4)%;|YG;Od6tMXT0<%8sOdk>79YiQt8 znXiBxe%oZMQp#yFUxLEBQzH2FkNp-w@PX^!;m*5g0}D`%kb@xkinh7FP1TZ%YNTEm sS3zj88OPPqm9IV&Ngt_BsSrsgPJ~?@?bhO0Ab;PlY2MH%Qo9xYKWZvp4FCWD From dc798768a0a7d88c31228160b04d22480ee7929a Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 9 Dec 2013 17:58:17 -0800 Subject: [PATCH 017/189] update pdf --- publications/whitepaper/druid.pdf | Bin 540245 -> 540323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/publications/whitepaper/druid.pdf b/publications/whitepaper/druid.pdf index 6044d7d9c2411fc94ad7758b8aa1ff2b842f28c7..f9f0f6b1446e14bf3fe6bbb23586ff6042bcd7f1 100644 GIT binary patch delta 85350 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 delta 85310 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 NW`Q9i6O|W(`5&I%0B`^R From 8e0336ff836c5d59af3f01d3ad89788aa5afaeec Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 9 Dec 2013 18:01:11 -0800 Subject: [PATCH 018/189] fix formatting on paper --- publications/whitepaper/druid.pdf | Bin 540323 -> 540137 bytes publications/whitepaper/druid.tex | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/publications/whitepaper/druid.pdf b/publications/whitepaper/druid.pdf index f9f0f6b1446e14bf3fe6bbb23586ff6042bcd7f1..6d835efc381a1a3c28f28f5d714fc87335e612f0 100644 GIT binary patch delta 50879 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 diff --git a/publications/whitepaper/druid.tex b/publications/whitepaper/druid.tex index 4554a53bc6a..0d1305d51f4 100644 --- a/publications/whitepaper/druid.tex +++ b/publications/whitepaper/druid.tex @@ -325,7 +325,7 @@ serves whatever data it finds. \begin{figure} \centering -\includegraphics[width = 2.8in]{historical_download} +\includegraphics[width = 2.6in]{historical_download} \caption{Historical nodes download immutable segments from deep storage. Segments must be loaded in memory before they can be queried.} \label{fig:historical_download} \end{figure} From 303f6ff33498381d8455043f7b9d085ce42972fd Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 9 Dec 2013 18:25:29 -0800 Subject: [PATCH 019/189] fix worker config setup problems --- .../java/io/druid/indexing/worker/config/WorkerConfig.java | 2 +- services/src/main/java/io/druid/cli/CliOverlord.java | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/worker/config/WorkerConfig.java b/indexing-service/src/main/java/io/druid/indexing/worker/config/WorkerConfig.java index d510df4c3ee..567dd62aa1f 100644 --- a/indexing-service/src/main/java/io/druid/indexing/worker/config/WorkerConfig.java +++ b/indexing-service/src/main/java/io/druid/indexing/worker/config/WorkerConfig.java @@ -38,7 +38,7 @@ public class WorkerConfig @JsonProperty @Min(1) - private int capacity = Runtime.getRuntime().availableProcessors() - 1; + private int capacity = Math.max(1, Runtime.getRuntime().availableProcessors() - 1); public String getIp() { diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index c0a2d14b54c..c067f57711f 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -70,6 +70,7 @@ import io.druid.indexing.overlord.scaling.ResourceManagementStrategy; import io.druid.indexing.overlord.scaling.SimpleResourceManagementConfig; import io.druid.indexing.overlord.scaling.SimpleResourceManagementStrategy; import io.druid.indexing.overlord.setup.WorkerSetupData; +import io.druid.indexing.worker.config.WorkerConfig; import io.druid.server.http.RedirectFilter; import io.druid.server.http.RedirectInfo; import io.druid.server.initialization.JettyServerInitializer; @@ -166,6 +167,8 @@ public class CliOverlord extends ServerRunnable private void configureRunners(Binder binder) { + JsonConfigProvider.bind(binder, "druid.worker", WorkerConfig.class); + PolyBind.createChoice( binder, "druid.indexer.runner.type", From 976affb3af09ab82eec10b8990dbd0928c28c295 Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 9 Dec 2013 18:28:31 -0800 Subject: [PATCH 020/189] prepare for next release --- build.sh | 2 +- docs/content/Booting-a-production-cluster.md | 2 +- docs/content/Examples.md | 4 ++-- docs/content/Realtime.md | 2 +- docs/content/Tutorial:-A-First-Look-at-Druid.md | 4 ++-- docs/content/Tutorial:-Loading-Your-Data-Part-2.md | 2 +- docs/content/Tutorial:-The-Druid-Cluster.md | 6 +++--- docs/content/Tutorial:-Webstream.md | 4 ++-- docs/content/Twitter-Tutorial.textile | 2 +- examples/config/historical/runtime.properties | 2 +- examples/config/realtime/runtime.properties | 2 +- services/src/main/java/io/druid/cli/CliBroker.java | 2 +- services/src/main/java/io/druid/cli/CliCoordinator.java | 2 +- services/src/main/java/io/druid/cli/CliHadoopIndexer.java | 2 +- services/src/main/java/io/druid/cli/CliHistorical.java | 2 +- services/src/main/java/io/druid/cli/CliOverlord.java | 2 +- services/src/main/java/io/druid/cli/CliRealtime.java | 2 +- services/src/main/java/io/druid/cli/CliRealtimeExample.java | 2 +- 18 files changed, 23 insertions(+), 23 deletions(-) diff --git a/build.sh b/build.sh index 9b6148b6c4c..9f08138249e 100755 --- a/build.sh +++ b/build.sh @@ -30,4 +30,4 @@ echo "For examples, see: " echo " " ls -1 examples/*/*sh echo " " -echo "See also http://druid.io/docs/0.6.26" +echo "See also http://druid.io/docs/0.6.27" diff --git a/docs/content/Booting-a-production-cluster.md b/docs/content/Booting-a-production-cluster.md index 61755b5733d..674beb48fc8 100644 --- a/docs/content/Booting-a-production-cluster.md +++ b/docs/content/Booting-a-production-cluster.md @@ -3,7 +3,7 @@ layout: doc_page --- # Booting a Single Node Cluster # -[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.26-bin.tar.gz). +[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.27-bin.tar.gz). The [ec2 run script](https://github.com/metamx/druid/blob/master/examples/bin/run_ec2.sh), run_ec2.sh, is located at 'examples/bin' if you have checked out the code, or at the root of the project if you've downloaded a tarball. The scripts rely on the [Amazon EC2 API Tools](http://aws.amazon.com/developertools/351), and you will need to set three environment variables: diff --git a/docs/content/Examples.md b/docs/content/Examples.md index 81549a7ec2d..21ffa406618 100644 --- a/docs/content/Examples.md +++ b/docs/content/Examples.md @@ -19,13 +19,13 @@ Clone Druid and build it: git clone https://github.com/metamx/druid.git druid cd druid git fetch --tags -git checkout druid-0.6.26 +git checkout druid-0.6.27 ./build.sh ``` ### Downloading the DSK (Druid Standalone Kit) -[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.26-bin.tar.gz) a stand-alone tarball and run it: +[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.27-bin.tar.gz) a stand-alone tarball and run it: ``` bash tar -xzf druid-services-0.X.X-bin.tar.gz diff --git a/docs/content/Realtime.md b/docs/content/Realtime.md index 3d6c432add1..801cd0b63cd 100644 --- a/docs/content/Realtime.md +++ b/docs/content/Realtime.md @@ -27,7 +27,7 @@ druid.host=localhost druid.service=realtime druid.port=8083 -druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.26"] +druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.27"] druid.zk.service.host=localhost diff --git a/docs/content/Tutorial:-A-First-Look-at-Druid.md b/docs/content/Tutorial:-A-First-Look-at-Druid.md index 548d573c6f1..d22074fee6f 100644 --- a/docs/content/Tutorial:-A-First-Look-at-Druid.md +++ b/docs/content/Tutorial:-A-First-Look-at-Druid.md @@ -49,7 +49,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu ### Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.26-bin.tar.gz). Download this file to a directory of your choosing. +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.27-bin.tar.gz). Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -60,7 +60,7 @@ tar -zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.26 +cd druid-services-0.6.27 ``` You should see a bunch of files: diff --git a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md index 60d5487784d..a35615758ee 100644 --- a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md +++ b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md @@ -44,7 +44,7 @@ With real-world data, we recommend having a message bus such as [Apache Kafka](h #### Setting up Kafka -[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.26/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. +[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.27/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. Instructions for booting a Zookeeper and then Kafka cluster are available [here](http://kafka.apache.org/07/quickstart.html). diff --git a/docs/content/Tutorial:-The-Druid-Cluster.md b/docs/content/Tutorial:-The-Druid-Cluster.md index e954c3de257..32493577601 100644 --- a/docs/content/Tutorial:-The-Druid-Cluster.md +++ b/docs/content/Tutorial:-The-Druid-Cluster.md @@ -13,7 +13,7 @@ In this tutorial, we will set up other types of Druid nodes as well as and exter If you followed the first tutorial, you should already have Druid downloaded. If not, let's go back and do that first. -You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.26-bin.tar.gz) +You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.27-bin.tar.gz) and untar the contents within by issuing: @@ -149,7 +149,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.26"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.27"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b @@ -238,7 +238,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.26","io.druid.extensions:druid-kafka-seven:0.6.26"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.27","io.druid.extensions:druid-kafka-seven:0.6.27"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/docs/content/Tutorial:-Webstream.md b/docs/content/Tutorial:-Webstream.md index c8b83d1e00c..94d2d8938a2 100644 --- a/docs/content/Tutorial:-Webstream.md +++ b/docs/content/Tutorial:-Webstream.md @@ -37,7 +37,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.26-bin.tar.gz) +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.27-bin.tar.gz) Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -48,7 +48,7 @@ tar zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.26 +cd druid-services-0.6.27 ``` You should see a bunch of files: diff --git a/docs/content/Twitter-Tutorial.textile b/docs/content/Twitter-Tutorial.textile index 9e368b13f10..7f9fe689e0c 100644 --- a/docs/content/Twitter-Tutorial.textile +++ b/docs/content/Twitter-Tutorial.textile @@ -9,7 +9,7 @@ There are two ways to setup Druid: download a tarball, or build it from source. h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.26-bin.tar.gz. +We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.27-bin.tar.gz. Download this bad boy to a directory of your choosing. You can extract the awesomeness within by issuing: diff --git a/examples/config/historical/runtime.properties b/examples/config/historical/runtime.properties index 67cc44f74d7..71e60f7c48b 100644 --- a/examples/config/historical/runtime.properties +++ b/examples/config/historical/runtime.properties @@ -4,7 +4,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.26"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.27"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b diff --git a/examples/config/realtime/runtime.properties b/examples/config/realtime/runtime.properties index aefabeda473..ee548c8ed6b 100644 --- a/examples/config/realtime/runtime.properties +++ b/examples/config/realtime/runtime.properties @@ -4,7 +4,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.26","io.druid.extensions:druid-kafka-seven:0.6.26","io.druid.extensions:druid-rabbitmq:0.6.26"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.27","io.druid.extensions:druid-kafka-seven:0.6.27","io.druid.extensions:druid-rabbitmq:0.6.27"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/services/src/main/java/io/druid/cli/CliBroker.java b/services/src/main/java/io/druid/cli/CliBroker.java index 17ab46de12c..ca308e274c0 100644 --- a/services/src/main/java/io/druid/cli/CliBroker.java +++ b/services/src/main/java/io/druid/cli/CliBroker.java @@ -53,7 +53,7 @@ import java.util.List; */ @Command( name = "broker", - description = "Runs a broker node, see http://druid.io/docs/0.6.26/Broker.html for a description" + description = "Runs a broker node, see http://druid.io/docs/0.6.27/Broker.html for a description" ) public class CliBroker extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliCoordinator.java b/services/src/main/java/io/druid/cli/CliCoordinator.java index 937d11a88d7..532349ae849 100644 --- a/services/src/main/java/io/druid/cli/CliCoordinator.java +++ b/services/src/main/java/io/druid/cli/CliCoordinator.java @@ -63,7 +63,7 @@ import java.util.List; */ @Command( name = "coordinator", - description = "Runs the Coordinator, see http://druid.io/docs/0.6.26/Coordinator.html for a description." + description = "Runs the Coordinator, see http://druid.io/docs/0.6.27/Coordinator.html for a description." ) public class CliCoordinator extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java index 580bdd8f3da..9b45d6fc242 100644 --- a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java +++ b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java @@ -41,7 +41,7 @@ import java.util.List; */ @Command( name = "hadoop", - description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.26/Batch-ingestion.html for a description." + description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.27/Batch-ingestion.html for a description." ) public class CliHadoopIndexer implements Runnable { diff --git a/services/src/main/java/io/druid/cli/CliHistorical.java b/services/src/main/java/io/druid/cli/CliHistorical.java index 15abef7cae5..f607d1d9da9 100644 --- a/services/src/main/java/io/druid/cli/CliHistorical.java +++ b/services/src/main/java/io/druid/cli/CliHistorical.java @@ -42,7 +42,7 @@ import java.util.List; */ @Command( name = "historical", - description = "Runs a Historical node, see http://druid.io/docs/0.6.26/Historical.html for a description" + description = "Runs a Historical node, see http://druid.io/docs/0.6.27/Historical.html for a description" ) public class CliHistorical extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index c067f57711f..3031f95a976 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -94,7 +94,7 @@ import java.util.List; */ @Command( name = "overlord", - description = "Runs an Overlord node, see http://druid.io/docs/0.6.26/Indexing-Service.html for a description" + description = "Runs an Overlord node, see http://druid.io/docs/0.6.27/Indexing-Service.html for a description" ) public class CliOverlord extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtime.java b/services/src/main/java/io/druid/cli/CliRealtime.java index e01a1f62bac..e712d151137 100644 --- a/services/src/main/java/io/druid/cli/CliRealtime.java +++ b/services/src/main/java/io/druid/cli/CliRealtime.java @@ -30,7 +30,7 @@ import java.util.List; */ @Command( name = "realtime", - description = "Runs a realtime node, see http://druid.io/docs/0.6.26/Realtime.html for a description" + description = "Runs a realtime node, see http://druid.io/docs/0.6.27/Realtime.html for a description" ) public class CliRealtime extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtimeExample.java b/services/src/main/java/io/druid/cli/CliRealtimeExample.java index 659810cffe6..e4d55890d79 100644 --- a/services/src/main/java/io/druid/cli/CliRealtimeExample.java +++ b/services/src/main/java/io/druid/cli/CliRealtimeExample.java @@ -42,7 +42,7 @@ import java.util.concurrent.Executor; */ @Command( name = "realtime", - description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.26/Realtime.html for a description" + description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.27/Realtime.html for a description" ) public class CliRealtimeExample extends ServerRunnable { From 71fe8a43da191174c7b967e33502f1dd76c49f17 Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 9 Dec 2013 18:30:11 -0800 Subject: [PATCH 021/189] [maven-release-plugin] prepare release druid-0.6.27 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 1e49a41e6af..fc89f5e72f7 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27-SNAPSHOT + 0.6.27 diff --git a/common/pom.xml b/common/pom.xml index 47e7633c89d..770579e8265 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27-SNAPSHOT + 0.6.27 diff --git a/examples/pom.xml b/examples/pom.xml index f4205d8e594..6b29059a442 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27-SNAPSHOT + 0.6.27 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 32a24614d31..b453581fcee 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27-SNAPSHOT + 0.6.27 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index d2964f84c26..def8cc928df 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27-SNAPSHOT + 0.6.27 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index c5bb4042857..f8dabc56ab7 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27-SNAPSHOT + 0.6.27 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index aff29a6ec82..59c8bc5add8 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27-SNAPSHOT + 0.6.27 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 0367a8e2712..a83145d2f2d 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27-SNAPSHOT + 0.6.27 diff --git a/pom.xml b/pom.xml index ebe0203823d..a5f432bf247 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.27-SNAPSHOT + 0.6.27 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.27 diff --git a/processing/pom.xml b/processing/pom.xml index 14bf1b01d73..ffc9e57a17c 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27-SNAPSHOT + 0.6.27 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 8fc0ffe682f..52105c650d3 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.27-SNAPSHOT + 0.6.27 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 1b0f844136e..9d9a3ca402f 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27-SNAPSHOT + 0.6.27 diff --git a/server/pom.xml b/server/pom.xml index 8dd5037d3ae..1ec4f6acfac 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27-SNAPSHOT + 0.6.27 diff --git a/services/pom.xml b/services/pom.xml index 77b12502d6f..21fa99c4f3a 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.27-SNAPSHOT + 0.6.27 From c03d573e84a34689819a1a7f93a4bb2a45e8ec9a Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 9 Dec 2013 18:30:15 -0800 Subject: [PATCH 022/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index fc89f5e72f7..c9c16032ee3 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27 + 0.6.28-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index 770579e8265..93bba8efab5 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27 + 0.6.28-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index 6b29059a442..19f1e58ae3f 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27 + 0.6.28-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index b453581fcee..8039de6aae0 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27 + 0.6.28-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index def8cc928df..e08c263cc96 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27 + 0.6.28-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index f8dabc56ab7..46b15bb5cca 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27 + 0.6.28-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 59c8bc5add8..aab0dcbdc43 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27 + 0.6.28-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index a83145d2f2d..cef8d267f16 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27 + 0.6.28-SNAPSHOT diff --git a/pom.xml b/pom.xml index a5f432bf247..54c6738d7fc 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.27 + 0.6.28-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.27 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index ffc9e57a17c..2a42b43da17 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27 + 0.6.28-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 52105c650d3..6e3e5dc553b 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.27 + 0.6.28-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 9d9a3ca402f..c66e8b5f6cf 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27 + 0.6.28-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index 1ec4f6acfac..8cf2f4e1ec7 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.27 + 0.6.28-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index 21fa99c4f3a..0c627bcbe06 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.27 + 0.6.28-SNAPSHOT From 939d2313c04f18e728726e611d3aaa20b6aeb321 Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 9 Dec 2013 18:35:18 -0800 Subject: [PATCH 023/189] [maven-release-plugin] prepare release druid-0.6.28 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index c9c16032ee3..c1c6c976782 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28-SNAPSHOT + 0.6.28 diff --git a/common/pom.xml b/common/pom.xml index 93bba8efab5..b14efab82a1 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28-SNAPSHOT + 0.6.28 diff --git a/examples/pom.xml b/examples/pom.xml index 19f1e58ae3f..840bbeccbc6 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28-SNAPSHOT + 0.6.28 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 8039de6aae0..fabd6af4f35 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28-SNAPSHOT + 0.6.28 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index e08c263cc96..cd7f9dc4e8d 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28-SNAPSHOT + 0.6.28 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 46b15bb5cca..c2b83ea3910 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28-SNAPSHOT + 0.6.28 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index aab0dcbdc43..9422d262390 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28-SNAPSHOT + 0.6.28 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index cef8d267f16..009860fe003 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28-SNAPSHOT + 0.6.28 diff --git a/pom.xml b/pom.xml index 54c6738d7fc..b111734807f 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.28-SNAPSHOT + 0.6.28 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.28 diff --git a/processing/pom.xml b/processing/pom.xml index 2a42b43da17..aa2da39cafd 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28-SNAPSHOT + 0.6.28 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 6e3e5dc553b..6d50c7e656d 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.28-SNAPSHOT + 0.6.28 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index c66e8b5f6cf..ff96970c33b 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28-SNAPSHOT + 0.6.28 diff --git a/server/pom.xml b/server/pom.xml index 8cf2f4e1ec7..38b77f061f6 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28-SNAPSHOT + 0.6.28 diff --git a/services/pom.xml b/services/pom.xml index 0c627bcbe06..b6fda9cb490 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.28-SNAPSHOT + 0.6.28 From 98a0f40227d9713b55b75d9050c7961300f7d52b Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 9 Dec 2013 18:35:22 -0800 Subject: [PATCH 024/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index c1c6c976782..52ee5ee7bbb 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28 + 0.6.29-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index b14efab82a1..6ec4761c21f 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28 + 0.6.29-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index 840bbeccbc6..c1dbf523d35 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28 + 0.6.29-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index fabd6af4f35..4fc7b286b1d 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28 + 0.6.29-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index cd7f9dc4e8d..c67c69e68e4 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28 + 0.6.29-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index c2b83ea3910..f54100c6855 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28 + 0.6.29-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 9422d262390..048edd01c56 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28 + 0.6.29-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 009860fe003..9e0145486d2 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28 + 0.6.29-SNAPSHOT diff --git a/pom.xml b/pom.xml index b111734807f..36fab9a5c0c 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.28 + 0.6.29-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.28 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index aa2da39cafd..4c6a96d1270 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28 + 0.6.29-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 6d50c7e656d..00f9bd81445 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.28 + 0.6.29-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index ff96970c33b..41a68d756cf 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28 + 0.6.29-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index 38b77f061f6..2ed2da0aee1 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.28 + 0.6.29-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index b6fda9cb490..83cb62f963d 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.28 + 0.6.29-SNAPSHOT From bb69f8adcd839d52a61844601df5813ca453e703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Mon, 9 Dec 2013 18:22:30 -0800 Subject: [PATCH 025/189] fix JS dim extraction null handling + test case --- .../extraction/JavascriptDimExtractionFn.java | 3 ++- .../JavascriptDimExtractionFnTest.java | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/processing/src/main/java/io/druid/query/extraction/JavascriptDimExtractionFn.java b/processing/src/main/java/io/druid/query/extraction/JavascriptDimExtractionFn.java index 1878df5479e..f552ef4e9cd 100644 --- a/processing/src/main/java/io/druid/query/extraction/JavascriptDimExtractionFn.java +++ b/processing/src/main/java/io/druid/query/extraction/JavascriptDimExtractionFn.java @@ -51,7 +51,8 @@ public class JavascriptDimExtractionFn implements DimExtractionFn cx = contextFactory.enterContext(); } - return Context.toString(fn.call(cx, scope, scope, new String[]{input})); + final Object res = fn.call(cx, scope, scope, new String[]{input}); + return res != null ? Context.toString(res) : null; } }; } diff --git a/processing/src/test/java/io/druid/query/extraction/extraction/JavascriptDimExtractionFnTest.java b/processing/src/test/java/io/druid/query/extraction/extraction/JavascriptDimExtractionFnTest.java index cc5a1b26b4e..22e403588fd 100644 --- a/processing/src/test/java/io/druid/query/extraction/extraction/JavascriptDimExtractionFnTest.java +++ b/processing/src/test/java/io/druid/query/extraction/extraction/JavascriptDimExtractionFnTest.java @@ -20,6 +20,7 @@ package io.druid.query.extraction.extraction; import com.google.common.collect.Iterators; +import com.google.common.collect.Lists; import io.druid.query.extraction.DimExtractionFn; import io.druid.query.extraction.JavascriptDimExtractionFn; import org.junit.Assert; @@ -52,6 +53,21 @@ public class JavascriptDimExtractionFnTest } } + @Test + public void testCastingAndNull() + { + String function = "function(x) {\n x = Number(x);\n if(isNaN(x)) return null;\n return Math.floor(x / 5) * 5;\n}"; + DimExtractionFn dimExtractionFn = new JavascriptDimExtractionFn(function); + + Iterator it = Iterators.forArray("0", "5", "5", "10", null); + + for(String str : Lists.newArrayList("1", "5", "6", "10", "CA")) { + String res = dimExtractionFn.apply(str); + String expected = it.next(); + Assert.assertEquals(expected, res); + } + } + @Test public void testJavascriptRegex() { From ab1f02b2bc358feb7569c55fee8be9bd1d2fce0f Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 9 Dec 2013 18:38:22 -0800 Subject: [PATCH 026/189] fix version brokenness and prepare for next release --- build.sh | 2 +- docs/content/Booting-a-production-cluster.md | 2 +- docs/content/Examples.md | 4 ++-- docs/content/Realtime.md | 2 +- docs/content/Tutorial:-A-First-Look-at-Druid.md | 4 ++-- docs/content/Tutorial:-Loading-Your-Data-Part-2.md | 2 +- docs/content/Tutorial:-The-Druid-Cluster.md | 6 +++--- docs/content/Tutorial:-Webstream.md | 4 ++-- docs/content/Twitter-Tutorial.textile | 2 +- examples/config/historical/runtime.properties | 2 +- examples/config/realtime/runtime.properties | 2 +- services/src/main/java/io/druid/cli/CliBroker.java | 2 +- services/src/main/java/io/druid/cli/CliCoordinator.java | 2 +- services/src/main/java/io/druid/cli/CliHadoopIndexer.java | 2 +- services/src/main/java/io/druid/cli/CliHistorical.java | 2 +- services/src/main/java/io/druid/cli/CliOverlord.java | 2 +- services/src/main/java/io/druid/cli/CliRealtime.java | 2 +- services/src/main/java/io/druid/cli/CliRealtimeExample.java | 2 +- 18 files changed, 23 insertions(+), 23 deletions(-) diff --git a/build.sh b/build.sh index 9f08138249e..cc2c1dc1331 100755 --- a/build.sh +++ b/build.sh @@ -30,4 +30,4 @@ echo "For examples, see: " echo " " ls -1 examples/*/*sh echo " " -echo "See also http://druid.io/docs/0.6.27" +echo "See also http://druid.io/docs/0.6.29" diff --git a/docs/content/Booting-a-production-cluster.md b/docs/content/Booting-a-production-cluster.md index 674beb48fc8..b8f4c554288 100644 --- a/docs/content/Booting-a-production-cluster.md +++ b/docs/content/Booting-a-production-cluster.md @@ -3,7 +3,7 @@ layout: doc_page --- # Booting a Single Node Cluster # -[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.27-bin.tar.gz). +[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.29-bin.tar.gz). The [ec2 run script](https://github.com/metamx/druid/blob/master/examples/bin/run_ec2.sh), run_ec2.sh, is located at 'examples/bin' if you have checked out the code, or at the root of the project if you've downloaded a tarball. The scripts rely on the [Amazon EC2 API Tools](http://aws.amazon.com/developertools/351), and you will need to set three environment variables: diff --git a/docs/content/Examples.md b/docs/content/Examples.md index 21ffa406618..76bbc796402 100644 --- a/docs/content/Examples.md +++ b/docs/content/Examples.md @@ -19,13 +19,13 @@ Clone Druid and build it: git clone https://github.com/metamx/druid.git druid cd druid git fetch --tags -git checkout druid-0.6.27 +git checkout druid-0.6.29 ./build.sh ``` ### Downloading the DSK (Druid Standalone Kit) -[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.27-bin.tar.gz) a stand-alone tarball and run it: +[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.29-bin.tar.gz) a stand-alone tarball and run it: ``` bash tar -xzf druid-services-0.X.X-bin.tar.gz diff --git a/docs/content/Realtime.md b/docs/content/Realtime.md index 801cd0b63cd..b3560331a06 100644 --- a/docs/content/Realtime.md +++ b/docs/content/Realtime.md @@ -27,7 +27,7 @@ druid.host=localhost druid.service=realtime druid.port=8083 -druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.27"] +druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.29"] druid.zk.service.host=localhost diff --git a/docs/content/Tutorial:-A-First-Look-at-Druid.md b/docs/content/Tutorial:-A-First-Look-at-Druid.md index d22074fee6f..c5459b393c8 100644 --- a/docs/content/Tutorial:-A-First-Look-at-Druid.md +++ b/docs/content/Tutorial:-A-First-Look-at-Druid.md @@ -49,7 +49,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu ### Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.27-bin.tar.gz). Download this file to a directory of your choosing. +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.29-bin.tar.gz). Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -60,7 +60,7 @@ tar -zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.27 +cd druid-services-0.6.29 ``` You should see a bunch of files: diff --git a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md index a35615758ee..c9e1b344837 100644 --- a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md +++ b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md @@ -44,7 +44,7 @@ With real-world data, we recommend having a message bus such as [Apache Kafka](h #### Setting up Kafka -[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.27/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. +[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.29/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. Instructions for booting a Zookeeper and then Kafka cluster are available [here](http://kafka.apache.org/07/quickstart.html). diff --git a/docs/content/Tutorial:-The-Druid-Cluster.md b/docs/content/Tutorial:-The-Druid-Cluster.md index 32493577601..78cc8fb9a8f 100644 --- a/docs/content/Tutorial:-The-Druid-Cluster.md +++ b/docs/content/Tutorial:-The-Druid-Cluster.md @@ -13,7 +13,7 @@ In this tutorial, we will set up other types of Druid nodes as well as and exter If you followed the first tutorial, you should already have Druid downloaded. If not, let's go back and do that first. -You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.27-bin.tar.gz) +You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.29-bin.tar.gz) and untar the contents within by issuing: @@ -149,7 +149,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.27"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.29"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b @@ -238,7 +238,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.27","io.druid.extensions:druid-kafka-seven:0.6.27"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.29","io.druid.extensions:druid-kafka-seven:0.6.29"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/docs/content/Tutorial:-Webstream.md b/docs/content/Tutorial:-Webstream.md index 94d2d8938a2..a6ad9b84be4 100644 --- a/docs/content/Tutorial:-Webstream.md +++ b/docs/content/Tutorial:-Webstream.md @@ -37,7 +37,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.27-bin.tar.gz) +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.29-bin.tar.gz) Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -48,7 +48,7 @@ tar zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.27 +cd druid-services-0.6.29 ``` You should see a bunch of files: diff --git a/docs/content/Twitter-Tutorial.textile b/docs/content/Twitter-Tutorial.textile index 7f9fe689e0c..1152f58289c 100644 --- a/docs/content/Twitter-Tutorial.textile +++ b/docs/content/Twitter-Tutorial.textile @@ -9,7 +9,7 @@ There are two ways to setup Druid: download a tarball, or build it from source. h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.27-bin.tar.gz. +We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.29-bin.tar.gz. Download this bad boy to a directory of your choosing. You can extract the awesomeness within by issuing: diff --git a/examples/config/historical/runtime.properties b/examples/config/historical/runtime.properties index 71e60f7c48b..c8679b2b0aa 100644 --- a/examples/config/historical/runtime.properties +++ b/examples/config/historical/runtime.properties @@ -4,7 +4,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.27"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.29"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b diff --git a/examples/config/realtime/runtime.properties b/examples/config/realtime/runtime.properties index ee548c8ed6b..21144989d41 100644 --- a/examples/config/realtime/runtime.properties +++ b/examples/config/realtime/runtime.properties @@ -4,7 +4,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.27","io.druid.extensions:druid-kafka-seven:0.6.27","io.druid.extensions:druid-rabbitmq:0.6.27"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.29","io.druid.extensions:druid-kafka-seven:0.6.29","io.druid.extensions:druid-rabbitmq:0.6.29"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/services/src/main/java/io/druid/cli/CliBroker.java b/services/src/main/java/io/druid/cli/CliBroker.java index ca308e274c0..4ab89f7177d 100644 --- a/services/src/main/java/io/druid/cli/CliBroker.java +++ b/services/src/main/java/io/druid/cli/CliBroker.java @@ -53,7 +53,7 @@ import java.util.List; */ @Command( name = "broker", - description = "Runs a broker node, see http://druid.io/docs/0.6.27/Broker.html for a description" + description = "Runs a broker node, see http://druid.io/docs/0.6.29/Broker.html for a description" ) public class CliBroker extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliCoordinator.java b/services/src/main/java/io/druid/cli/CliCoordinator.java index 532349ae849..539572b97c5 100644 --- a/services/src/main/java/io/druid/cli/CliCoordinator.java +++ b/services/src/main/java/io/druid/cli/CliCoordinator.java @@ -63,7 +63,7 @@ import java.util.List; */ @Command( name = "coordinator", - description = "Runs the Coordinator, see http://druid.io/docs/0.6.27/Coordinator.html for a description." + description = "Runs the Coordinator, see http://druid.io/docs/0.6.29/Coordinator.html for a description." ) public class CliCoordinator extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java index 9b45d6fc242..75bb507683c 100644 --- a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java +++ b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java @@ -41,7 +41,7 @@ import java.util.List; */ @Command( name = "hadoop", - description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.27/Batch-ingestion.html for a description." + description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.29/Batch-ingestion.html for a description." ) public class CliHadoopIndexer implements Runnable { diff --git a/services/src/main/java/io/druid/cli/CliHistorical.java b/services/src/main/java/io/druid/cli/CliHistorical.java index f607d1d9da9..8b64bc1b8e0 100644 --- a/services/src/main/java/io/druid/cli/CliHistorical.java +++ b/services/src/main/java/io/druid/cli/CliHistorical.java @@ -42,7 +42,7 @@ import java.util.List; */ @Command( name = "historical", - description = "Runs a Historical node, see http://druid.io/docs/0.6.27/Historical.html for a description" + description = "Runs a Historical node, see http://druid.io/docs/0.6.29/Historical.html for a description" ) public class CliHistorical extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index 3031f95a976..22dae7207cd 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -94,7 +94,7 @@ import java.util.List; */ @Command( name = "overlord", - description = "Runs an Overlord node, see http://druid.io/docs/0.6.27/Indexing-Service.html for a description" + description = "Runs an Overlord node, see http://druid.io/docs/0.6.29/Indexing-Service.html for a description" ) public class CliOverlord extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtime.java b/services/src/main/java/io/druid/cli/CliRealtime.java index e712d151137..232b2e94d0a 100644 --- a/services/src/main/java/io/druid/cli/CliRealtime.java +++ b/services/src/main/java/io/druid/cli/CliRealtime.java @@ -30,7 +30,7 @@ import java.util.List; */ @Command( name = "realtime", - description = "Runs a realtime node, see http://druid.io/docs/0.6.27/Realtime.html for a description" + description = "Runs a realtime node, see http://druid.io/docs/0.6.29/Realtime.html for a description" ) public class CliRealtime extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtimeExample.java b/services/src/main/java/io/druid/cli/CliRealtimeExample.java index e4d55890d79..40423e343b5 100644 --- a/services/src/main/java/io/druid/cli/CliRealtimeExample.java +++ b/services/src/main/java/io/druid/cli/CliRealtimeExample.java @@ -42,7 +42,7 @@ import java.util.concurrent.Executor; */ @Command( name = "realtime", - description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.27/Realtime.html for a description" + description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.29/Realtime.html for a description" ) public class CliRealtimeExample extends ServerRunnable { From 829658f17190221c699af10074d051fb8f283eec Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 9 Dec 2013 18:45:23 -0800 Subject: [PATCH 027/189] [maven-release-plugin] prepare release druid-0.6.29 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 52ee5ee7bbb..e86f91a808b 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29-SNAPSHOT + 0.6.29 diff --git a/common/pom.xml b/common/pom.xml index 6ec4761c21f..cf1a32c2522 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29-SNAPSHOT + 0.6.29 diff --git a/examples/pom.xml b/examples/pom.xml index c1dbf523d35..f2a7b4b70c3 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29-SNAPSHOT + 0.6.29 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 4fc7b286b1d..2da15734045 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29-SNAPSHOT + 0.6.29 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index c67c69e68e4..8ca93a06c85 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29-SNAPSHOT + 0.6.29 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index f54100c6855..38ca8473199 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29-SNAPSHOT + 0.6.29 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 048edd01c56..b1926c8b225 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29-SNAPSHOT + 0.6.29 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 9e0145486d2..63d7663e7e8 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29-SNAPSHOT + 0.6.29 diff --git a/pom.xml b/pom.xml index 36fab9a5c0c..f9b261e7f30 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.29-SNAPSHOT + 0.6.29 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.29 diff --git a/processing/pom.xml b/processing/pom.xml index 4c6a96d1270..2bd1f8339c9 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29-SNAPSHOT + 0.6.29 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 00f9bd81445..a1426e23245 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.29-SNAPSHOT + 0.6.29 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 41a68d756cf..6b4235fdf0f 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29-SNAPSHOT + 0.6.29 diff --git a/server/pom.xml b/server/pom.xml index 2ed2da0aee1..a1cf16bde20 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29-SNAPSHOT + 0.6.29 diff --git a/services/pom.xml b/services/pom.xml index 83cb62f963d..5018dcc3682 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.29-SNAPSHOT + 0.6.29 From 7a134272b39648f6be5c8294301f92a8e52bf3aa Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 9 Dec 2013 18:45:27 -0800 Subject: [PATCH 028/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index e86f91a808b..776470e2da1 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29 + 0.6.30-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index cf1a32c2522..4ada36fc609 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29 + 0.6.30-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index f2a7b4b70c3..8a462776e85 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29 + 0.6.30-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 2da15734045..3e23a9581e0 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29 + 0.6.30-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 8ca93a06c85..eb97d5c912c 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29 + 0.6.30-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 38ca8473199..82752fba860 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29 + 0.6.30-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index b1926c8b225..8346a018c5c 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29 + 0.6.30-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 63d7663e7e8..70f985c16fa 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29 + 0.6.30-SNAPSHOT diff --git a/pom.xml b/pom.xml index f9b261e7f30..36cbb23c058 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.29 + 0.6.30-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.29 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index 2bd1f8339c9..0afb9c8df8b 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29 + 0.6.30-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index a1426e23245..5f99e191209 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.29 + 0.6.30-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 6b4235fdf0f..e931ab0033f 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29 + 0.6.30-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index a1cf16bde20..abd4db0423c 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.29 + 0.6.30-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index 5018dcc3682..c215ab61559 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.29 + 0.6.30-SNAPSHOT From a262d8bf1136ca2036e29a205e790b53a57e961f Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 9 Dec 2013 18:56:30 -0800 Subject: [PATCH 029/189] fix curator shutdown problem in curator inv manager test --- .../curator/inventory/CuratorInventoryManagerTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/src/test/java/io/druid/curator/inventory/CuratorInventoryManagerTest.java b/server/src/test/java/io/druid/curator/inventory/CuratorInventoryManagerTest.java index 1c13475912c..e325b2c59df 100644 --- a/server/src/test/java/io/druid/curator/inventory/CuratorInventoryManagerTest.java +++ b/server/src/test/java/io/druid/curator/inventory/CuratorInventoryManagerTest.java @@ -29,6 +29,7 @@ import org.apache.curator.framework.api.CuratorEventType; import org.apache.curator.framework.api.CuratorListener; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.Watcher; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -50,6 +51,12 @@ public class CuratorInventoryManagerTest extends io.druid.curator.CuratorTestBas exec = Execs.singleThreaded("curator-inventory-manager-test-%s"); } + @After + public void tearDown() throws Exception + { + tearDownServerAndCurator(); + } + @Test public void testSanity() throws Exception { From 6cc0860657914ffbb25c3104213dd1c0ed7269d7 Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 9 Dec 2013 20:05:55 -0800 Subject: [PATCH 030/189] fix s3 module problems and prepare for next release --- build.sh | 2 +- docs/content/Booting-a-production-cluster.md | 2 +- docs/content/Examples.md | 4 ++-- docs/content/Realtime.md | 2 +- docs/content/Tutorial:-A-First-Look-at-Druid.md | 4 ++-- docs/content/Tutorial:-Loading-Your-Data-Part-2.md | 2 +- docs/content/Tutorial:-The-Druid-Cluster.md | 6 +++--- docs/content/Tutorial:-Webstream.md | 4 ++-- docs/content/Twitter-Tutorial.textile | 2 +- examples/config/historical/runtime.properties | 2 +- examples/config/realtime/runtime.properties | 2 +- pom.xml | 2 +- .../java/io/druid/firehose/s3/StaticS3FirehoseFactory.java | 6 +++--- services/src/main/java/io/druid/cli/CliBroker.java | 2 +- services/src/main/java/io/druid/cli/CliCoordinator.java | 2 +- services/src/main/java/io/druid/cli/CliHadoopIndexer.java | 2 +- services/src/main/java/io/druid/cli/CliHistorical.java | 2 +- services/src/main/java/io/druid/cli/CliOverlord.java | 2 +- services/src/main/java/io/druid/cli/CliRealtime.java | 2 +- services/src/main/java/io/druid/cli/CliRealtimeExample.java | 2 +- 20 files changed, 27 insertions(+), 27 deletions(-) diff --git a/build.sh b/build.sh index cc2c1dc1331..ea05e4af3bd 100755 --- a/build.sh +++ b/build.sh @@ -30,4 +30,4 @@ echo "For examples, see: " echo " " ls -1 examples/*/*sh echo " " -echo "See also http://druid.io/docs/0.6.29" +echo "See also http://druid.io/docs/0.6.30" diff --git a/docs/content/Booting-a-production-cluster.md b/docs/content/Booting-a-production-cluster.md index b8f4c554288..4405944b8b9 100644 --- a/docs/content/Booting-a-production-cluster.md +++ b/docs/content/Booting-a-production-cluster.md @@ -3,7 +3,7 @@ layout: doc_page --- # Booting a Single Node Cluster # -[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.29-bin.tar.gz). +[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.30-bin.tar.gz). The [ec2 run script](https://github.com/metamx/druid/blob/master/examples/bin/run_ec2.sh), run_ec2.sh, is located at 'examples/bin' if you have checked out the code, or at the root of the project if you've downloaded a tarball. The scripts rely on the [Amazon EC2 API Tools](http://aws.amazon.com/developertools/351), and you will need to set three environment variables: diff --git a/docs/content/Examples.md b/docs/content/Examples.md index 76bbc796402..e4d3ea6f6f7 100644 --- a/docs/content/Examples.md +++ b/docs/content/Examples.md @@ -19,13 +19,13 @@ Clone Druid and build it: git clone https://github.com/metamx/druid.git druid cd druid git fetch --tags -git checkout druid-0.6.29 +git checkout druid-0.6.30 ./build.sh ``` ### Downloading the DSK (Druid Standalone Kit) -[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.29-bin.tar.gz) a stand-alone tarball and run it: +[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.30-bin.tar.gz) a stand-alone tarball and run it: ``` bash tar -xzf druid-services-0.X.X-bin.tar.gz diff --git a/docs/content/Realtime.md b/docs/content/Realtime.md index b3560331a06..aaaff7c7840 100644 --- a/docs/content/Realtime.md +++ b/docs/content/Realtime.md @@ -27,7 +27,7 @@ druid.host=localhost druid.service=realtime druid.port=8083 -druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.29"] +druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.30"] druid.zk.service.host=localhost diff --git a/docs/content/Tutorial:-A-First-Look-at-Druid.md b/docs/content/Tutorial:-A-First-Look-at-Druid.md index c5459b393c8..9703918e7df 100644 --- a/docs/content/Tutorial:-A-First-Look-at-Druid.md +++ b/docs/content/Tutorial:-A-First-Look-at-Druid.md @@ -49,7 +49,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu ### Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.29-bin.tar.gz). Download this file to a directory of your choosing. +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.30-bin.tar.gz). Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -60,7 +60,7 @@ tar -zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.29 +cd druid-services-0.6.30 ``` You should see a bunch of files: diff --git a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md index c9e1b344837..a6eecc14f0a 100644 --- a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md +++ b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md @@ -44,7 +44,7 @@ With real-world data, we recommend having a message bus such as [Apache Kafka](h #### Setting up Kafka -[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.29/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. +[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.30/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. Instructions for booting a Zookeeper and then Kafka cluster are available [here](http://kafka.apache.org/07/quickstart.html). diff --git a/docs/content/Tutorial:-The-Druid-Cluster.md b/docs/content/Tutorial:-The-Druid-Cluster.md index 78cc8fb9a8f..f084e0efdc6 100644 --- a/docs/content/Tutorial:-The-Druid-Cluster.md +++ b/docs/content/Tutorial:-The-Druid-Cluster.md @@ -13,7 +13,7 @@ In this tutorial, we will set up other types of Druid nodes as well as and exter If you followed the first tutorial, you should already have Druid downloaded. If not, let's go back and do that first. -You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.29-bin.tar.gz) +You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.30-bin.tar.gz) and untar the contents within by issuing: @@ -149,7 +149,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.29"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.30"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b @@ -238,7 +238,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.29","io.druid.extensions:druid-kafka-seven:0.6.29"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.30","io.druid.extensions:druid-kafka-seven:0.6.30"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/docs/content/Tutorial:-Webstream.md b/docs/content/Tutorial:-Webstream.md index a6ad9b84be4..97987247b00 100644 --- a/docs/content/Tutorial:-Webstream.md +++ b/docs/content/Tutorial:-Webstream.md @@ -37,7 +37,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.29-bin.tar.gz) +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.30-bin.tar.gz) Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -48,7 +48,7 @@ tar zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.29 +cd druid-services-0.6.30 ``` You should see a bunch of files: diff --git a/docs/content/Twitter-Tutorial.textile b/docs/content/Twitter-Tutorial.textile index 1152f58289c..1fbe5b3c038 100644 --- a/docs/content/Twitter-Tutorial.textile +++ b/docs/content/Twitter-Tutorial.textile @@ -9,7 +9,7 @@ There are two ways to setup Druid: download a tarball, or build it from source. h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.29-bin.tar.gz. +We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.30-bin.tar.gz. Download this bad boy to a directory of your choosing. You can extract the awesomeness within by issuing: diff --git a/examples/config/historical/runtime.properties b/examples/config/historical/runtime.properties index c8679b2b0aa..e8ec335789e 100644 --- a/examples/config/historical/runtime.properties +++ b/examples/config/historical/runtime.properties @@ -4,7 +4,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.29"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.30"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b diff --git a/examples/config/realtime/runtime.properties b/examples/config/realtime/runtime.properties index 21144989d41..b694b40dd58 100644 --- a/examples/config/realtime/runtime.properties +++ b/examples/config/realtime/runtime.properties @@ -4,7 +4,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.29","io.druid.extensions:druid-kafka-seven:0.6.29","io.druid.extensions:druid-rabbitmq:0.6.29"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.30","io.druid.extensions:druid-kafka-seven:0.6.30","io.druid.extensions:druid-rabbitmq:0.6.30"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/pom.xml b/pom.xml index 36cbb23c058..c1e93e7fcf8 100644 --- a/pom.xml +++ b/pom.xml @@ -41,7 +41,7 @@ UTF-8 0.25.0 2.1.0-incubating - 0.1.3 + 0.1.5 diff --git a/s3-extensions/src/main/java/io/druid/firehose/s3/StaticS3FirehoseFactory.java b/s3-extensions/src/main/java/io/druid/firehose/s3/StaticS3FirehoseFactory.java index e4d6741f414..9797a28b6d4 100644 --- a/s3-extensions/src/main/java/io/druid/firehose/s3/StaticS3FirehoseFactory.java +++ b/s3-extensions/src/main/java/io/druid/firehose/s3/StaticS3FirehoseFactory.java @@ -34,7 +34,7 @@ import io.druid.data.input.impl.FileIteratingFirehose; import io.druid.data.input.impl.StringInputRowParser; import org.apache.commons.io.IOUtils; import org.apache.commons.io.LineIterator; -import org.jets3t.service.S3Service; +import org.jets3t.service.impl.rest.httpclient.RestS3Service; import org.jets3t.service.model.S3Bucket; import org.jets3t.service.model.S3Object; @@ -55,13 +55,13 @@ public class StaticS3FirehoseFactory implements FirehoseFactory { private static final Logger log = new Logger(StaticS3FirehoseFactory.class); - private final S3Service s3Client; + private final RestS3Service s3Client; private final StringInputRowParser parser; private final List uris; @JsonCreator public StaticS3FirehoseFactory( - @JacksonInject("s3Client") S3Service s3Client, + @JacksonInject("s3Client") RestS3Service s3Client, @JsonProperty("parser") StringInputRowParser parser, @JsonProperty("uris") List uris ) diff --git a/services/src/main/java/io/druid/cli/CliBroker.java b/services/src/main/java/io/druid/cli/CliBroker.java index 4ab89f7177d..41ddbbc9b03 100644 --- a/services/src/main/java/io/druid/cli/CliBroker.java +++ b/services/src/main/java/io/druid/cli/CliBroker.java @@ -53,7 +53,7 @@ import java.util.List; */ @Command( name = "broker", - description = "Runs a broker node, see http://druid.io/docs/0.6.29/Broker.html for a description" + description = "Runs a broker node, see http://druid.io/docs/0.6.30/Broker.html for a description" ) public class CliBroker extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliCoordinator.java b/services/src/main/java/io/druid/cli/CliCoordinator.java index 539572b97c5..013c2c59d91 100644 --- a/services/src/main/java/io/druid/cli/CliCoordinator.java +++ b/services/src/main/java/io/druid/cli/CliCoordinator.java @@ -63,7 +63,7 @@ import java.util.List; */ @Command( name = "coordinator", - description = "Runs the Coordinator, see http://druid.io/docs/0.6.29/Coordinator.html for a description." + description = "Runs the Coordinator, see http://druid.io/docs/0.6.30/Coordinator.html for a description." ) public class CliCoordinator extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java index 75bb507683c..1fb6ef7df99 100644 --- a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java +++ b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java @@ -41,7 +41,7 @@ import java.util.List; */ @Command( name = "hadoop", - description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.29/Batch-ingestion.html for a description." + description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.30/Batch-ingestion.html for a description." ) public class CliHadoopIndexer implements Runnable { diff --git a/services/src/main/java/io/druid/cli/CliHistorical.java b/services/src/main/java/io/druid/cli/CliHistorical.java index 8b64bc1b8e0..0d2b75ed62d 100644 --- a/services/src/main/java/io/druid/cli/CliHistorical.java +++ b/services/src/main/java/io/druid/cli/CliHistorical.java @@ -42,7 +42,7 @@ import java.util.List; */ @Command( name = "historical", - description = "Runs a Historical node, see http://druid.io/docs/0.6.29/Historical.html for a description" + description = "Runs a Historical node, see http://druid.io/docs/0.6.30/Historical.html for a description" ) public class CliHistorical extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index 22dae7207cd..ebee2eeea21 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -94,7 +94,7 @@ import java.util.List; */ @Command( name = "overlord", - description = "Runs an Overlord node, see http://druid.io/docs/0.6.29/Indexing-Service.html for a description" + description = "Runs an Overlord node, see http://druid.io/docs/0.6.30/Indexing-Service.html for a description" ) public class CliOverlord extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtime.java b/services/src/main/java/io/druid/cli/CliRealtime.java index 232b2e94d0a..1765451e5e8 100644 --- a/services/src/main/java/io/druid/cli/CliRealtime.java +++ b/services/src/main/java/io/druid/cli/CliRealtime.java @@ -30,7 +30,7 @@ import java.util.List; */ @Command( name = "realtime", - description = "Runs a realtime node, see http://druid.io/docs/0.6.29/Realtime.html for a description" + description = "Runs a realtime node, see http://druid.io/docs/0.6.30/Realtime.html for a description" ) public class CliRealtime extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtimeExample.java b/services/src/main/java/io/druid/cli/CliRealtimeExample.java index 40423e343b5..7a0abc2fc55 100644 --- a/services/src/main/java/io/druid/cli/CliRealtimeExample.java +++ b/services/src/main/java/io/druid/cli/CliRealtimeExample.java @@ -42,7 +42,7 @@ import java.util.concurrent.Executor; */ @Command( name = "realtime", - description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.29/Realtime.html for a description" + description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.30/Realtime.html for a description" ) public class CliRealtimeExample extends ServerRunnable { From 54755b88645fa8e07f47df9293d7c2c9916b4033 Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 9 Dec 2013 20:07:34 -0800 Subject: [PATCH 031/189] [maven-release-plugin] prepare release druid-0.6.30 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 776470e2da1..413e78ca39f 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30-SNAPSHOT + 0.6.30 diff --git a/common/pom.xml b/common/pom.xml index 4ada36fc609..f14c4d46abd 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30-SNAPSHOT + 0.6.30 diff --git a/examples/pom.xml b/examples/pom.xml index 8a462776e85..f7ad6ef5741 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30-SNAPSHOT + 0.6.30 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 3e23a9581e0..2e1b4e211bd 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30-SNAPSHOT + 0.6.30 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index eb97d5c912c..312c85f3915 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30-SNAPSHOT + 0.6.30 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 82752fba860..a7f2a68705a 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30-SNAPSHOT + 0.6.30 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 8346a018c5c..71049549793 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30-SNAPSHOT + 0.6.30 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 70f985c16fa..afcd6bcd495 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30-SNAPSHOT + 0.6.30 diff --git a/pom.xml b/pom.xml index c1e93e7fcf8..5b9d86acc86 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.30-SNAPSHOT + 0.6.30 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.30 diff --git a/processing/pom.xml b/processing/pom.xml index 0afb9c8df8b..bbde0493a87 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30-SNAPSHOT + 0.6.30 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 5f99e191209..730407dc2fd 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.30-SNAPSHOT + 0.6.30 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index e931ab0033f..e11844eb308 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30-SNAPSHOT + 0.6.30 diff --git a/server/pom.xml b/server/pom.xml index abd4db0423c..613718c4ac5 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30-SNAPSHOT + 0.6.30 diff --git a/services/pom.xml b/services/pom.xml index c215ab61559..4707e472cbe 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.30-SNAPSHOT + 0.6.30 From a7cc5596236d53a101f22ef78080beebb63d5152 Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 9 Dec 2013 20:07:38 -0800 Subject: [PATCH 032/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 413e78ca39f..72dd5923d2f 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30 + 0.6.31-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index f14c4d46abd..3337214b3c4 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30 + 0.6.31-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index f7ad6ef5741..563aff36a2b 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30 + 0.6.31-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 2e1b4e211bd..0e0ca99990b 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30 + 0.6.31-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 312c85f3915..261c5e2d4ec 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30 + 0.6.31-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index a7f2a68705a..4f3433cae3d 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30 + 0.6.31-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 71049549793..a7c589553ee 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30 + 0.6.31-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index afcd6bcd495..ad722b50cdd 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30 + 0.6.31-SNAPSHOT diff --git a/pom.xml b/pom.xml index 5b9d86acc86..702f796bff1 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.30 + 0.6.31-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.30 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index bbde0493a87..f4bbe0ccd54 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30 + 0.6.31-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 730407dc2fd..c8f5963ebba 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.30 + 0.6.31-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index e11844eb308..24f82396316 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30 + 0.6.31-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index 613718c4ac5..e05f8fd7b15 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.30 + 0.6.31-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index 4707e472cbe..e0f8975f7e5 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.30 + 0.6.31-SNAPSHOT From 47c1c8cab2bb132720729d46431e01349eefdd98 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Mon, 2 Dec 2013 15:05:33 -0800 Subject: [PATCH 033/189] TaskStorage: Rename getRunningTasks -> getActiveTasks --- .../main/java/io/druid/indexing/overlord/DbTaskStorage.java | 2 +- .../io/druid/indexing/overlord/HeapMemoryTaskStorage.java | 2 +- .../src/main/java/io/druid/indexing/overlord/TaskQueue.java | 2 +- .../src/main/java/io/druid/indexing/overlord/TaskStorage.java | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java b/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java index ec2e2f99feb..92e1043c8eb 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java @@ -220,7 +220,7 @@ public class DbTaskStorage implements TaskStorage } @Override - public List getRunningTasks() + public List getActiveTasks() { return dbi.withHandle( new HandleCallback>() diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/HeapMemoryTaskStorage.java b/indexing-service/src/main/java/io/druid/indexing/overlord/HeapMemoryTaskStorage.java index 7ebaa69dab9..ef23972ebe4 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/HeapMemoryTaskStorage.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/HeapMemoryTaskStorage.java @@ -128,7 +128,7 @@ public class HeapMemoryTaskStorage implements TaskStorage } @Override - public List getRunningTasks() + public List getActiveTasks() { giant.lock(); diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskQueue.java b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskQueue.java index 580dfd02a0d..a78aef84f6e 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskQueue.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskQueue.java @@ -100,7 +100,7 @@ public class TaskQueue // Get all running tasks and their locks final Multimap tasksByLock = ArrayListMultimap.create(); - for (final Task task : taskStorage.getRunningTasks()) { + for (final Task task : taskStorage.getActiveTasks()) { try { final List taskLocks = taskStorage.getLocks(task.getId()); diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorage.java b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorage.java index b74dc0d9c1a..3e3cbf46cdc 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorage.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorage.java @@ -77,9 +77,9 @@ public interface TaskStorage public List getAuditLogs(String taskid); /** - * Returns a list of currently-running tasks as stored in the storage facility, in no particular order. + * Returns a list of currently running or pending tasks as stored in the storage facility, in no particular order. */ - public List getRunningTasks(); + public List getActiveTasks(); /** * Returns a list of locks for a particular task. From f3cfd1d78124457e03eaf1854da20a3d081fd3d9 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Tue, 10 Dec 2013 17:38:36 -0800 Subject: [PATCH 034/189] Introduce FileTaskLogs, and move TaskLogs module from server to indexing-service --- .../guice/IndexingServiceTaskLogsModule.java | 7 +- .../common/config/FileTaskLogsConfig.java | 18 +++++ .../common/tasklogs/FileTaskLogs.java | 68 +++++++++++++++++++ .../druid/initialization/Initialization.java | 2 - .../java/io/druid/cli/CliMiddleManager.java | 4 +- .../main/java/io/druid/cli/CliOverlord.java | 4 +- 6 files changed, 98 insertions(+), 5 deletions(-) rename server/src/main/java/io/druid/guice/TaskLogsModule.java => indexing-service/src/main/java/io/druid/guice/IndexingServiceTaskLogsModule.java (79%) create mode 100644 indexing-service/src/main/java/io/druid/indexing/common/config/FileTaskLogsConfig.java create mode 100644 indexing-service/src/main/java/io/druid/indexing/common/tasklogs/FileTaskLogs.java diff --git a/server/src/main/java/io/druid/guice/TaskLogsModule.java b/indexing-service/src/main/java/io/druid/guice/IndexingServiceTaskLogsModule.java similarity index 79% rename from server/src/main/java/io/druid/guice/TaskLogsModule.java rename to indexing-service/src/main/java/io/druid/guice/IndexingServiceTaskLogsModule.java index eedd12caabc..6d9b0e038b1 100644 --- a/server/src/main/java/io/druid/guice/TaskLogsModule.java +++ b/indexing-service/src/main/java/io/druid/guice/IndexingServiceTaskLogsModule.java @@ -23,13 +23,15 @@ import com.google.inject.Binder; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.multibindings.MapBinder; +import io.druid.indexing.common.config.FileTaskLogsConfig; +import io.druid.indexing.common.tasklogs.FileTaskLogs; import io.druid.tasklogs.NoopTaskLogs; import io.druid.tasklogs.TaskLogPusher; import io.druid.tasklogs.TaskLogs; /** */ -public class TaskLogsModule implements Module +public class IndexingServiceTaskLogsModule implements Module { @Override public void configure(Binder binder) @@ -38,7 +40,10 @@ public class TaskLogsModule implements Module final MapBinder taskLogBinder = Binders.taskLogsBinder(binder); taskLogBinder.addBinding("noop").to(NoopTaskLogs.class).in(LazySingleton.class); + taskLogBinder.addBinding("file").to(FileTaskLogs.class).in(LazySingleton.class); binder.bind(NoopTaskLogs.class).in(LazySingleton.class); + binder.bind(FileTaskLogs.class).in(LazySingleton.class); + JsonConfigProvider.bind(binder, "druid.indexer.logs", FileTaskLogsConfig.class); binder.bind(TaskLogPusher.class).to(TaskLogs.class); } diff --git a/indexing-service/src/main/java/io/druid/indexing/common/config/FileTaskLogsConfig.java b/indexing-service/src/main/java/io/druid/indexing/common/config/FileTaskLogsConfig.java new file mode 100644 index 00000000000..a07bade39cf --- /dev/null +++ b/indexing-service/src/main/java/io/druid/indexing/common/config/FileTaskLogsConfig.java @@ -0,0 +1,18 @@ +package io.druid.indexing.common.config; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import javax.validation.constraints.NotNull; +import java.io.File; + +public class FileTaskLogsConfig +{ + @JsonProperty + @NotNull + private File directory = new File("log"); + + public File getDirectory() + { + return directory; + } +} diff --git a/indexing-service/src/main/java/io/druid/indexing/common/tasklogs/FileTaskLogs.java b/indexing-service/src/main/java/io/druid/indexing/common/tasklogs/FileTaskLogs.java new file mode 100644 index 00000000000..771f918463b --- /dev/null +++ b/indexing-service/src/main/java/io/druid/indexing/common/tasklogs/FileTaskLogs.java @@ -0,0 +1,68 @@ +package io.druid.indexing.common.tasklogs; + +import com.google.common.base.Optional; +import com.google.common.io.ByteStreams; +import com.google.common.io.Files; +import com.google.common.io.InputSupplier; +import com.google.inject.Inject; +import com.metamx.common.logger.Logger; +import io.druid.indexing.common.config.FileTaskLogsConfig; +import io.druid.tasklogs.TaskLogs; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class FileTaskLogs implements TaskLogs +{ + private static final Logger log = new Logger(FileTaskLogs.class); + + private final FileTaskLogsConfig config; + + @Inject + public FileTaskLogs( + FileTaskLogsConfig config + ) + { + this.config = config; + } + + @Override + public void pushTaskLog(final String taskid, File file) throws IOException + { + if (!config.getDirectory().exists()) { + config.getDirectory().mkdir(); + } + final File outputFile = fileForTask(taskid); + Files.copy(file, outputFile); + log.info("Wrote task log to: %s", outputFile); + } + + @Override + public Optional> streamTaskLog(final String taskid, final long offset) throws IOException + { + final File file = fileForTask(taskid); + if (file.exists()) { + return Optional.>of( + new InputSupplier() + { + @Override + public InputStream getInput() throws IOException + { + final InputStream inputStream = new FileInputStream(file); + ByteStreams.skipFully(inputStream, offset); + return inputStream; + } + } + ); + } else { + return Optional.absent(); + } + } + + private File fileForTask(final String taskid) + { + return new File(config.getDirectory(), String.format("%s.log", taskid)); + } +} diff --git a/server/src/main/java/io/druid/initialization/Initialization.java b/server/src/main/java/io/druid/initialization/Initialization.java index 408cc2c1d0d..bb30718a33f 100644 --- a/server/src/main/java/io/druid/initialization/Initialization.java +++ b/server/src/main/java/io/druid/initialization/Initialization.java @@ -52,7 +52,6 @@ import io.druid.guice.QueryableModule; import io.druid.guice.ServerModule; import io.druid.guice.ServerViewModule; import io.druid.guice.StorageNodeModule; -import io.druid.guice.TaskLogsModule; import io.druid.guice.annotations.Client; import io.druid.guice.annotations.Json; import io.druid.guice.annotations.Smile; @@ -299,7 +298,6 @@ public class Initialization new JacksonConfigManagerModule(), new IndexingServiceDiscoveryModule(), new DataSegmentPusherPullerModule(), - new TaskLogsModule(), new FirehoseModule() ); diff --git a/services/src/main/java/io/druid/cli/CliMiddleManager.java b/services/src/main/java/io/druid/cli/CliMiddleManager.java index 95319e7ee55..d0867b42aa6 100644 --- a/services/src/main/java/io/druid/cli/CliMiddleManager.java +++ b/services/src/main/java/io/druid/cli/CliMiddleManager.java @@ -28,6 +28,7 @@ import com.metamx.common.logger.Logger; import io.airlift.command.Command; import io.druid.guice.IndexingServiceFirehoseModule; import io.druid.guice.IndexingServiceModuleHelper; +import io.druid.guice.IndexingServiceTaskLogsModule; import io.druid.guice.Jerseys; import io.druid.guice.JsonConfigProvider; import io.druid.guice.LazySingleton; @@ -103,7 +104,8 @@ public class CliMiddleManager extends ServerRunnable ); } }, - new IndexingServiceFirehoseModule() + new IndexingServiceFirehoseModule(), + new IndexingServiceTaskLogsModule() ); } } diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index ebee2eeea21..3e1313c51ba 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -32,6 +32,7 @@ import com.metamx.common.logger.Logger; import io.airlift.command.Command; import io.druid.guice.IndexingServiceFirehoseModule; import io.druid.guice.IndexingServiceModuleHelper; +import io.druid.guice.IndexingServiceTaskLogsModule; import io.druid.guice.JacksonConfigProvider; import io.druid.guice.Jerseys; import io.druid.guice.JsonConfigProvider; @@ -211,7 +212,8 @@ public class CliOverlord extends ServerRunnable JsonConfigProvider.bind(binder, "druid.indexer.autoscale", SimpleResourceManagementConfig.class); } }, - new IndexingServiceFirehoseModule() + new IndexingServiceFirehoseModule(), + new IndexingServiceTaskLogsModule() ); } From 96f679f31c18ece7c4a4c69f18f6760ed04df517 Mon Sep 17 00:00:00 2001 From: fjy Date: Tue, 10 Dec 2013 17:51:13 -0800 Subject: [PATCH 035/189] clean up for merge --- .../guice/IndexingServiceTaskLogsModule.java | 2 +- .../common/config/FileTaskLogsConfig.java | 19 +++++++++++++++++++ .../common/tasklogs/FileTaskLogs.java | 19 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/indexing-service/src/main/java/io/druid/guice/IndexingServiceTaskLogsModule.java b/indexing-service/src/main/java/io/druid/guice/IndexingServiceTaskLogsModule.java index 6d9b0e038b1..33452d0dfd9 100644 --- a/indexing-service/src/main/java/io/druid/guice/IndexingServiceTaskLogsModule.java +++ b/indexing-service/src/main/java/io/druid/guice/IndexingServiceTaskLogsModule.java @@ -36,7 +36,7 @@ public class IndexingServiceTaskLogsModule implements Module @Override public void configure(Binder binder) { - PolyBind.createChoice(binder, "druid.indexer.logs.type", Key.get(TaskLogs.class), Key.get(NoopTaskLogs.class)); + PolyBind.createChoice(binder, "druid.indexer.logs.type", Key.get(TaskLogs.class), Key.get(FileTaskLogs.class)); final MapBinder taskLogBinder = Binders.taskLogsBinder(binder); taskLogBinder.addBinding("noop").to(NoopTaskLogs.class).in(LazySingleton.class); diff --git a/indexing-service/src/main/java/io/druid/indexing/common/config/FileTaskLogsConfig.java b/indexing-service/src/main/java/io/druid/indexing/common/config/FileTaskLogsConfig.java index a07bade39cf..dfc7c9a9951 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/config/FileTaskLogsConfig.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/config/FileTaskLogsConfig.java @@ -1,3 +1,22 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + package io.druid.indexing.common.config; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/indexing-service/src/main/java/io/druid/indexing/common/tasklogs/FileTaskLogs.java b/indexing-service/src/main/java/io/druid/indexing/common/tasklogs/FileTaskLogs.java index 771f918463b..e1649b46f32 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/tasklogs/FileTaskLogs.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/tasklogs/FileTaskLogs.java @@ -1,3 +1,22 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + package io.druid.indexing.common.tasklogs; import com.google.common.base.Optional; From 60577a6e24cb0cc261acf466173a6451ef3a4a8e Mon Sep 17 00:00:00 2001 From: Igal Levy Date: Wed, 11 Dec 2013 09:16:49 -0800 Subject: [PATCH 036/189] adjusted size of first section head to match others --- docs/content/toc.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/toc.textile b/docs/content/toc.textile index 3b058a95c2c..ae0da14bcc7 100644 --- a/docs/content/toc.textile +++ b/docs/content/toc.textile @@ -3,7 +3,7 @@ -h1. Introduction +h2. Introduction * "About Druid":./ * "Concepts and Terminology":./Concepts-and-Terminology.html From 1ec6d5b0ef0aff6e5ae3a90c681a4d1d2e8bf35d Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 11 Dec 2013 15:04:32 -0800 Subject: [PATCH 037/189] S3Utils: Use RetryUtils from java-util --- pom.xml | 2 +- .../druid/storage/s3/S3DataSegmentPuller.java | 17 ++-- .../druid/storage/s3/S3DataSegmentPusher.java | 2 +- .../java/io/druid/storage/s3/S3Utils.java | 92 +++++++------------ 4 files changed, 43 insertions(+), 70 deletions(-) diff --git a/pom.xml b/pom.xml index 702f796bff1..db67db72a75 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ UTF-8 - 0.25.0 + 0.25.1 2.1.0-incubating 0.1.5 diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentPuller.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentPuller.java index ff286e262ac..1e310a9b90f 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentPuller.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentPuller.java @@ -160,15 +160,12 @@ public class S3DataSegmentPuller implements DataSegmentPuller } ); } - catch (InterruptedException e) { + catch (S3ServiceException | IOException e) { + throw new SegmentLoadingException(e, "S3 fail! Key[%s]", coords); + } + catch (Exception e) { throw Throwables.propagate(e); } - catch (IOException e) { - throw new SegmentLoadingException(e, "S3 fail! Key[%s]", coords); - } - catch (S3ServiceException e) { - throw new SegmentLoadingException(e, "S3 fail! Key[%s]", coords); - } } @Override @@ -188,12 +185,12 @@ public class S3DataSegmentPuller implements DataSegmentPuller ); return objDetails.getLastModifiedDate().getTime(); } - catch (InterruptedException e) { - throw Throwables.propagate(e); - } catch (S3ServiceException | IOException e) { throw new SegmentLoadingException(e, e.getMessage()); } + catch (Exception e) { + throw Throwables.propagate(e); + } } private static class S3Coords diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentPusher.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentPusher.java index 735a9365628..e8b1a99710f 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentPusher.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentPusher.java @@ -138,7 +138,7 @@ public class S3DataSegmentPusher implements DataSegmentPusher catch (ServiceException e) { throw new IOException(e); } - catch (InterruptedException e) { + catch (Exception e) { throw Throwables.propagate(e); } } diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3Utils.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3Utils.java index 598153ec778..619ad737cfe 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3Utils.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3Utils.java @@ -19,15 +19,14 @@ package io.druid.storage.s3; -import com.google.common.base.Throwables; -import com.metamx.common.logger.Logger; +import com.google.common.base.Predicate; +import com.metamx.common.RetryUtils; import org.jets3t.service.S3ServiceException; import org.jets3t.service.impl.rest.httpclient.RestS3Service; import org.jets3t.service.model.S3Bucket; import org.jets3t.service.model.S3Object; import java.io.IOException; -import java.util.Random; import java.util.concurrent.Callable; /** @@ -35,8 +34,6 @@ import java.util.concurrent.Callable; */ public class S3Utils { - private static final Logger log = new Logger(S3Utils.class); - public static void closeStreamsQuietly(S3Object s3Obj) { if (s3Obj == null) { @@ -55,68 +52,47 @@ public class S3Utils * Retries S3 operations that fail due to io-related exceptions. Service-level exceptions (access denied, file not * found, etc) are not retried. */ - public static T retryS3Operation(Callable f) throws IOException, S3ServiceException, InterruptedException + public static T retryS3Operation(Callable f) throws Exception { - int nTry = 0; + final Predicate shouldRetry = new Predicate() + { + @Override + public boolean apply(Throwable e) + { + if (e instanceof IOException) { + return true; + } else if (e instanceof S3ServiceException) { + final boolean isIOException = e.getCause() instanceof IOException; + final boolean isTimeout = "RequestTimeout".equals(((S3ServiceException) e).getS3ErrorCode()); + return isIOException || isTimeout; + } else { + return false; + } + } + }; final int maxTries = 10; - while (true) { - try { - nTry++; - return f.call(); - } - catch (IOException e) { - if (nTry <= maxTries) { - awaitNextRetry(e, nTry); - } else { - throw e; - } - } - catch (S3ServiceException e) { - if (nTry <= maxTries && - (e.getCause() instanceof IOException || - (e.getS3ErrorCode() != null && e.getS3ErrorCode().equals("RequestTimeout")))) { - awaitNextRetry(e, nTry); - } else { - throw e; - } - } - catch (Exception e) { - throw Throwables.propagate(e); - } - } - } - - private static void awaitNextRetry(Exception e, int nTry) throws InterruptedException - { - final long baseSleepMillis = 1000; - final long maxSleepMillis = 60000; - final double fuzzyMultiplier = Math.min(Math.max(1 + 0.2 * new Random().nextGaussian(), 0), 2); - final long sleepMillis = (long) (Math.min(maxSleepMillis, baseSleepMillis * Math.pow(2, nTry)) * fuzzyMultiplier); - log.warn("S3 fail on try %d, retrying in %,dms.", nTry, sleepMillis); - Thread.sleep(sleepMillis); + return RetryUtils.retry(f, shouldRetry, maxTries); } public static boolean isObjectInBucket(RestS3Service s3Client, String bucketName, String objectKey) throws S3ServiceException { - try { - s3Client.getObjectDetails(new S3Bucket(bucketName), objectKey); + try { + s3Client.getObjectDetails(new S3Bucket(bucketName), objectKey); + } + catch (S3ServiceException e) { + if (404 == e.getResponseCode() + || "NoSuchKey".equals(e.getS3ErrorCode()) + || "NoSuchBucket".equals(e.getS3ErrorCode())) { + return false; } - catch (S3ServiceException e) { - if (404 == e.getResponseCode() - || "NoSuchKey".equals(e.getS3ErrorCode()) - || "NoSuchBucket".equals(e.getS3ErrorCode())) - { - return false; - } - if ("AccessDenied".equals(e.getS3ErrorCode())) - { - // Object is inaccessible to current user, but does exist. - return true; - } - // Something else has gone wrong - throw e; + if ("AccessDenied".equals(e.getS3ErrorCode())) { + // Object is inaccessible to current user, but does exist. + return true; } + // Something else has gone wrong + throw e; + } return true; } From b2ea216bdf263b63e2643a8ef7c529d1c33e7d3c Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 11 Dec 2013 15:05:03 -0800 Subject: [PATCH 038/189] HttpClientModule: Default http timeout to PT5M --- server/src/main/java/io/druid/guice/HttpClientModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/io/druid/guice/HttpClientModule.java b/server/src/main/java/io/druid/guice/HttpClientModule.java index 8bf7c70b698..700aff7baee 100644 --- a/server/src/main/java/io/druid/guice/HttpClientModule.java +++ b/server/src/main/java/io/druid/guice/HttpClientModule.java @@ -103,7 +103,7 @@ public class HttpClientModule implements Module private int numConnections = 5; @JsonProperty - private Period readTimeout = null; + private Period readTimeout = new Period("PT5M"); public int getNumConnections() { From c4b8c8bc6f1433dfa89cbd09f6210efbe839aad4 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 11 Dec 2013 15:03:51 -0800 Subject: [PATCH 039/189] Rework indexing service internals to hopefully be more reliable. The TaskQueue directly manages the TaskRunner. The main management loop runs periodically and checks that the runner is doing reasonable things. If not, it attempts to adjust the runner. The management loop also runs on-demand when a task is added to keep task assignment relatively low latency. The TaskConsumer is no longer necessary and so it no longer exists. Task interval locks are handled differently. Instead of some tasks acquiring locks at runtime and some tasks having implicit fixed lock intervals, all tasks ask for locks explicitly. This occurs either in "isReady" (which runs on the overlord) or in "run" (which runs on the peon). Other changes: - The TaskQueue is attached to the leader lifecycle, instead of global - The TaskLockbox is able to sync itself from storage and is no longer bootstrapped by the TaskQueue. - RemoteTaskRunner does not clean up zk paths until asked to. This will prevent deletion of statuses that have not yet been committed. - Added retries on DbTaskStorage operations. - Removed SpawnTasksAction (no more subtasks) - Removed obsolete EventReceiverFirehose configs - Removed obsolete OldOverlordResource - Removed TaskStorageQueryAdapter methods related to subtasks --- .../guice/IndexingServiceFirehoseModule.java | 3 - .../common/actions/LockAcquireAction.java | 2 +- .../common/actions/LockReleaseAction.java | 2 +- ...sAction.java => LockTryAcquireAction.java} | 40 +- .../indexing/common/actions/TaskAction.java | 3 +- .../common/actions/TaskActionToolbox.java | 9 - .../EventReceiverFirehoseFactoryConfig.java | 31 - .../common/config/IndexerZkConfig.java | 33 - .../indexing/common/config/TaskLogConfig.java | 39 -- .../index/EventReceiverFirehoseFactory.java | 28 +- .../task/AbstractFixedIntervalTask.java | 56 ++ .../indexing/common/task/AbstractTask.java | 49 +- .../indexing/common/task/DeleteTask.java | 8 +- .../indexing/common/task/HadoopIndexTask.java | 14 +- .../task/IndexDeterminePartitionsTask.java | 299 --------- .../common/task/IndexGeneratorTask.java | 240 ------- .../druid/indexing/common/task/IndexTask.java | 365 +++++++++-- .../druid/indexing/common/task/KillTask.java | 21 +- .../indexing/common/task/MergeTaskBase.java | 26 +- .../druid/indexing/common/task/NoopTask.java | 14 +- .../common/task/RealtimeIndexTask.java | 10 +- .../io/druid/indexing/common/task/Task.java | 40 +- .../common/task/VersionConverterTask.java | 88 +-- .../indexing/overlord/DbTaskStorage.java | 97 ++- .../indexing/overlord/ForkingTaskRunner.java | 14 +- .../indexing/overlord/RemoteTaskRunner.java | 158 +++-- .../druid/indexing/overlord/TaskLockbox.java | 125 +++- .../druid/indexing/overlord/TaskMaster.java | 189 +++--- .../io/druid/indexing/overlord/TaskQueue.java | 583 +++++++++++------- .../druid/indexing/overlord/TaskRunner.java | 20 +- .../druid/indexing/overlord/TaskStorage.java | 3 +- .../overlord/TaskStorageQueryAdapter.java | 123 +--- .../overlord/ThreadPoolTaskRunner.java | 18 +- .../config/IndexerDbConnectorConfig.java | 39 -- .../overlord/config/TaskQueueConfig.java | 60 ++ .../indexing/overlord/exec/TaskConsumer.java | 204 ------ .../overlord/http/OldOverlordResource.java | 46 -- .../overlord/http/OverlordResource.java | 17 +- .../common/task/MergeTaskBaseTest.java | 2 +- .../indexing/common/task/TaskSerdeTest.java | 137 ++-- .../indexing/overlord/RealtimeishTask.java | 13 +- .../overlord/RemoteTaskRunnerTest.java | 5 - .../indexing/overlord/TaskLifecycleTest.java | 44 +- .../indexing/overlord/TaskQueueTest.java | 438 ------------- .../main/java/io/druid/cli/CliOverlord.java | 8 +- .../src/main/java/io/druid/cli/CliPeon.java | 1 - 46 files changed, 1375 insertions(+), 2389 deletions(-) rename indexing-service/src/main/java/io/druid/indexing/common/actions/{SpawnTasksAction.java => LockTryAcquireAction.java} (64%) delete mode 100644 indexing-service/src/main/java/io/druid/indexing/common/config/EventReceiverFirehoseFactoryConfig.java delete mode 100644 indexing-service/src/main/java/io/druid/indexing/common/config/IndexerZkConfig.java delete mode 100644 indexing-service/src/main/java/io/druid/indexing/common/config/TaskLogConfig.java create mode 100644 indexing-service/src/main/java/io/druid/indexing/common/task/AbstractFixedIntervalTask.java delete mode 100644 indexing-service/src/main/java/io/druid/indexing/common/task/IndexDeterminePartitionsTask.java delete mode 100644 indexing-service/src/main/java/io/druid/indexing/common/task/IndexGeneratorTask.java delete mode 100644 indexing-service/src/main/java/io/druid/indexing/overlord/config/IndexerDbConnectorConfig.java create mode 100644 indexing-service/src/main/java/io/druid/indexing/overlord/config/TaskQueueConfig.java delete mode 100644 indexing-service/src/main/java/io/druid/indexing/overlord/exec/TaskConsumer.java delete mode 100644 indexing-service/src/main/java/io/druid/indexing/overlord/http/OldOverlordResource.java delete mode 100644 indexing-service/src/test/java/io/druid/indexing/overlord/TaskQueueTest.java diff --git a/indexing-service/src/main/java/io/druid/guice/IndexingServiceFirehoseModule.java b/indexing-service/src/main/java/io/druid/guice/IndexingServiceFirehoseModule.java index 703179f4542..ed9f628452c 100644 --- a/indexing-service/src/main/java/io/druid/guice/IndexingServiceFirehoseModule.java +++ b/indexing-service/src/main/java/io/druid/guice/IndexingServiceFirehoseModule.java @@ -24,7 +24,6 @@ import com.fasterxml.jackson.databind.jsontype.NamedType; import com.fasterxml.jackson.databind.module.SimpleModule; import com.google.common.collect.ImmutableList; import com.google.inject.Binder; -import io.druid.indexing.common.config.EventReceiverFirehoseFactoryConfig; import io.druid.indexing.common.index.EventReceiverFirehoseFactory; import io.druid.initialization.DruidModule; @@ -46,7 +45,5 @@ public class IndexingServiceFirehoseModule implements DruidModule @Override public void configure(Binder binder) { - // backwards compatibility - ConfigProvider.bind(binder, EventReceiverFirehoseFactoryConfig.class); } } diff --git a/indexing-service/src/main/java/io/druid/indexing/common/actions/LockAcquireAction.java b/indexing-service/src/main/java/io/druid/indexing/common/actions/LockAcquireAction.java index 5730e3be082..5d600dcd369 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/actions/LockAcquireAction.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/actions/LockAcquireAction.java @@ -68,7 +68,7 @@ public class LockAcquireAction implements TaskAction @Override public boolean isAudited() { - return true; + return false; } @Override diff --git a/indexing-service/src/main/java/io/druid/indexing/common/actions/LockReleaseAction.java b/indexing-service/src/main/java/io/druid/indexing/common/actions/LockReleaseAction.java index 97397666d2b..6179c5ee658 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/actions/LockReleaseAction.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/actions/LockReleaseAction.java @@ -60,7 +60,7 @@ public class LockReleaseAction implements TaskAction @Override public boolean isAudited() { - return true; + return false; } @Override diff --git a/indexing-service/src/main/java/io/druid/indexing/common/actions/SpawnTasksAction.java b/indexing-service/src/main/java/io/druid/indexing/common/actions/LockTryAcquireAction.java similarity index 64% rename from indexing-service/src/main/java/io/druid/indexing/common/actions/SpawnTasksAction.java rename to indexing-service/src/main/java/io/druid/indexing/common/actions/LockTryAcquireAction.java index 85b1a53c275..699460af82f 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/actions/SpawnTasksAction.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/actions/LockTryAcquireAction.java @@ -23,56 +23,54 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.type.TypeReference; -import com.google.common.collect.ImmutableList; +import com.google.common.base.Optional; +import io.druid.indexing.common.TaskLock; import io.druid.indexing.common.task.Task; +import org.joda.time.Interval; -import java.util.List; - -public class SpawnTasksAction implements TaskAction +public class LockTryAcquireAction implements TaskAction> { @JsonIgnore - private final List newTasks; + private final Interval interval; @JsonCreator - public SpawnTasksAction( - @JsonProperty("newTasks") List newTasks + public LockTryAcquireAction( + @JsonProperty("interval") Interval interval ) { - this.newTasks = ImmutableList.copyOf(newTasks); + this.interval = interval; } @JsonProperty - public List getNewTasks() + public Interval getInterval() { - return newTasks; + return interval; } - public TypeReference getReturnTypeReference() + public TypeReference> getReturnTypeReference() { - return new TypeReference() {}; + return new TypeReference>() + { + }; } @Override - public Void perform(Task task, TaskActionToolbox toolbox) + public Optional perform(Task task, TaskActionToolbox toolbox) { - for(final Task newTask : newTasks) { - toolbox.getTaskQueue().add(newTask); - } - - return null; + return toolbox.getTaskLockbox().tryLock(task, interval); } @Override public boolean isAudited() { - return true; + return false; } @Override public String toString() { - return "SpawnTasksAction{" + - "newTasks=" + newTasks + + return "LockTryAcquireAction{" + + "interval=" + interval + '}'; } } diff --git a/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskAction.java b/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskAction.java index 37d4346247e..937315c0ad2 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskAction.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskAction.java @@ -34,8 +34,7 @@ import java.io.IOException; @JsonSubTypes.Type(name = "segmentInsertion", value = SegmentInsertAction.class), @JsonSubTypes.Type(name = "segmentListUsed", value = SegmentListUsedAction.class), @JsonSubTypes.Type(name = "segmentListUnused", value = SegmentListUnusedAction.class), - @JsonSubTypes.Type(name = "segmentNuke", value = SegmentNukeAction.class), - @JsonSubTypes.Type(name = "spawnTasks", value = SpawnTasksAction.class) + @JsonSubTypes.Type(name = "segmentNuke", value = SegmentNukeAction.class) }) public interface TaskAction { diff --git a/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskActionToolbox.java b/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskActionToolbox.java index b7e78e0c2be..a0b41e58a63 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskActionToolbox.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskActionToolbox.java @@ -27,7 +27,6 @@ import io.druid.indexing.common.TaskLock; import io.druid.indexing.common.task.Task; import io.druid.indexing.overlord.IndexerDBCoordinator; import io.druid.indexing.overlord.TaskLockbox; -import io.druid.indexing.overlord.TaskQueue; import io.druid.timeline.DataSegment; import java.util.List; @@ -35,30 +34,22 @@ import java.util.Set; public class TaskActionToolbox { - private final TaskQueue taskQueue; private final TaskLockbox taskLockbox; private final IndexerDBCoordinator indexerDBCoordinator; private final ServiceEmitter emitter; @Inject public TaskActionToolbox( - TaskQueue taskQueue, TaskLockbox taskLockbox, IndexerDBCoordinator indexerDBCoordinator, ServiceEmitter emitter ) { - this.taskQueue = taskQueue; this.taskLockbox = taskLockbox; this.indexerDBCoordinator = indexerDBCoordinator; this.emitter = emitter; } - public TaskQueue getTaskQueue() - { - return taskQueue; - } - public TaskLockbox getTaskLockbox() { return taskLockbox; diff --git a/indexing-service/src/main/java/io/druid/indexing/common/config/EventReceiverFirehoseFactoryConfig.java b/indexing-service/src/main/java/io/druid/indexing/common/config/EventReceiverFirehoseFactoryConfig.java deleted file mode 100644 index 5822bdb622a..00000000000 --- a/indexing-service/src/main/java/io/druid/indexing/common/config/EventReceiverFirehoseFactoryConfig.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Druid - a distributed column store. - * Copyright (C) 2012, 2013 Metamarkets Group Inc. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package io.druid.indexing.common.config; - -import org.skife.config.Config; - -/** - */ -@Deprecated -public abstract class EventReceiverFirehoseFactoryConfig -{ - @Config("druid.indexer.firehoseId.prefix") - public abstract String getFirehoseIdPrefix(); -} diff --git a/indexing-service/src/main/java/io/druid/indexing/common/config/IndexerZkConfig.java b/indexing-service/src/main/java/io/druid/indexing/common/config/IndexerZkConfig.java deleted file mode 100644 index 67a750ab535..00000000000 --- a/indexing-service/src/main/java/io/druid/indexing/common/config/IndexerZkConfig.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Druid - a distributed column store. - * Copyright (C) 2012, 2013 Metamarkets Group Inc. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package io.druid.indexing.common.config; - -import io.druid.server.initialization.ZkPathsConfig; -import org.skife.config.Config; -import org.skife.config.Default; - -/** - */ -public abstract class IndexerZkConfig extends ZkPathsConfig -{ - @Config("druid.zk.maxNumBytes") - @Default("512000") - public abstract long getMaxNumBytes(); -} diff --git a/indexing-service/src/main/java/io/druid/indexing/common/config/TaskLogConfig.java b/indexing-service/src/main/java/io/druid/indexing/common/config/TaskLogConfig.java deleted file mode 100644 index 787878e8eb8..00000000000 --- a/indexing-service/src/main/java/io/druid/indexing/common/config/TaskLogConfig.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Druid - a distributed column store. - * Copyright (C) 2012, 2013 Metamarkets Group Inc. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package io.druid.indexing.common.config; - -import org.skife.config.Config; -import org.skife.config.Default; -import org.skife.config.DefaultNull; - -public abstract class TaskLogConfig -{ - @Config("druid.indexer.logs.type") - @Default("noop") - public abstract String getLogType(); - - @Config("druid.indexer.logs.s3bucket") - @DefaultNull - public abstract String getLogStorageBucket(); - - @Config("druid.indexer.logs.s3prefix") - @DefaultNull - public abstract String getLogStoragePrefix(); -} diff --git a/indexing-service/src/main/java/io/druid/indexing/common/index/EventReceiverFirehoseFactory.java b/indexing-service/src/main/java/io/druid/indexing/common/index/EventReceiverFirehoseFactory.java index 0f3732ed386..b9e420f7020 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/index/EventReceiverFirehoseFactory.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/index/EventReceiverFirehoseFactory.java @@ -33,7 +33,6 @@ import io.druid.data.input.Firehose; import io.druid.data.input.FirehoseFactory; import io.druid.data.input.InputRow; import io.druid.data.input.impl.MapInputRowParser; -import io.druid.indexing.common.config.EventReceiverFirehoseFactoryConfig; import javax.ws.rs.POST; import javax.ws.rs.Path; @@ -63,31 +62,15 @@ public class EventReceiverFirehoseFactory implements FirehoseFactory private final MapInputRowParser parser; private final Optional chatHandlerProvider; - @Deprecated - private final EventReceiverFirehoseFactoryConfig config; - @JsonCreator public EventReceiverFirehoseFactory( @JsonProperty("serviceName") String serviceName, - @JsonProperty("firehoseId") String firehoseId, @JsonProperty("bufferSize") Integer bufferSize, @JsonProperty("parser") MapInputRowParser parser, - @JacksonInject ChatHandlerProvider chatHandlerProvider, - @JacksonInject EventReceiverFirehoseFactoryConfig config + @JacksonInject ChatHandlerProvider chatHandlerProvider ) { - // This code is here for backwards compatibility - if (serviceName == null) { - this.serviceName = String.format( - "%s:%s", - config.getFirehoseIdPrefix(), - Preconditions.checkNotNull(firehoseId, "firehoseId") - ); - } else { - this.serviceName = serviceName; - } - this.config = config; - + this.serviceName = Preconditions.checkNotNull(serviceName, "serviceName"); this.bufferSize = bufferSize == null || bufferSize <= 0 ? DEFAULT_BUFFER_SIZE : bufferSize; this.parser = Preconditions.checkNotNull(parser, "parser"); this.chatHandlerProvider = Optional.fromNullable(chatHandlerProvider); @@ -117,13 +100,6 @@ public class EventReceiverFirehoseFactory implements FirehoseFactory return serviceName; } - @Deprecated - @JsonProperty("firehoseId") - public String getFirehoseId() - { - return serviceName.replaceFirst(String.format("%s:", config.getFirehoseIdPrefix()), ""); - } - @JsonProperty public int getBufferSize() { diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/AbstractFixedIntervalTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/AbstractFixedIntervalTask.java new file mode 100644 index 00000000000..0b65988f216 --- /dev/null +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/AbstractFixedIntervalTask.java @@ -0,0 +1,56 @@ +package io.druid.indexing.common.task; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.druid.indexing.common.actions.LockTryAcquireAction; +import io.druid.indexing.common.actions.TaskActionClient; +import org.joda.time.Interval; + +public abstract class AbstractFixedIntervalTask extends AbstractTask +{ + @JsonIgnore + final Interval interval; + + protected AbstractFixedIntervalTask( + String id, + String dataSource, + Interval interval + ) + { + this(id, id, new TaskResource(id, 1), dataSource, interval); + } + + protected AbstractFixedIntervalTask( + String id, + String groupId, + String dataSource, + Interval interval + ) + { + this(id, groupId, new TaskResource(id, 1), dataSource, interval); + } + + protected AbstractFixedIntervalTask( + String id, + String groupId, + TaskResource taskResource, + String dataSource, + Interval interval + ) + { + super(id, groupId, taskResource, dataSource); + this.interval = interval; + } + + @Override + public boolean isReady(TaskActionClient taskActionClient) throws Exception + { + return taskActionClient.submit(new LockTryAcquireAction(interval)).isPresent(); + } + + @JsonProperty + public Interval getInterval() + { + return interval; + } +} diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/AbstractTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/AbstractTask.java index 1944243e7fe..eaff1b9b46f 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/AbstractTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/AbstractTask.java @@ -23,21 +23,15 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Joiner; import com.google.common.base.Objects; -import com.google.common.base.Optional; import com.google.common.base.Preconditions; import io.druid.indexing.common.TaskLock; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.TaskToolbox; -import io.druid.indexing.common.actions.LockAcquireAction; import io.druid.indexing.common.actions.LockListAction; -import io.druid.indexing.common.actions.SegmentListUsedAction; -import io.druid.indexing.common.actions.TaskActionClient; import io.druid.query.Query; import io.druid.query.QueryRunner; -import org.joda.time.Interval; import java.io.IOException; -import java.util.List; public abstract class AbstractTask implements Task { @@ -55,26 +49,22 @@ public abstract class AbstractTask implements Task @JsonIgnore private final String dataSource; - @JsonIgnore - private final Optional interval; - - protected AbstractTask(String id, String dataSource, Interval interval) + protected AbstractTask(String id, String dataSource) { - this(id, id, new TaskResource(id, 1), dataSource, interval); + this(id, id, new TaskResource(id, 1), dataSource); } - protected AbstractTask(String id, String groupId, String dataSource, Interval interval) + protected AbstractTask(String id, String groupId, String dataSource) { - this(id, groupId, new TaskResource(id, 1), dataSource, interval); + this(id, groupId, new TaskResource(id, 1), dataSource); } - protected AbstractTask(String id, String groupId, TaskResource taskResource, String dataSource, Interval interval) + protected AbstractTask(String id, String groupId, TaskResource taskResource, String dataSource) { this.id = Preconditions.checkNotNull(id, "id"); this.groupId = Preconditions.checkNotNull(groupId, "groupId"); this.taskResource = Preconditions.checkNotNull(taskResource, "resource"); this.dataSource = Preconditions.checkNotNull(dataSource, "dataSource"); - this.interval = Optional.fromNullable(interval); } @JsonProperty @@ -111,25 +101,12 @@ public abstract class AbstractTask implements Task return dataSource; } - @JsonProperty("interval") - @Override - public Optional getImplicitLockInterval() - { - return interval; - } - @Override public QueryRunner getQueryRunner(Query query) { return null; } - @Override - public TaskStatus preflight(TaskActionClient taskActionClient) throws Exception - { - return TaskStatus.running(id); - } - @Override public String toString() { @@ -137,7 +114,6 @@ public abstract class AbstractTask implements Task .add("id", id) .add("type", getType()) .add("dataSource", dataSource) - .add("interval", getImplicitLockInterval()) .toString(); } @@ -149,11 +125,6 @@ public abstract class AbstractTask implements Task return ID_JOINER.join(objects); } - public SegmentListUsedAction defaultListUsedAction() - { - return new SegmentListUsedAction(getDataSource(), getImplicitLockInterval().get()); - } - public TaskStatus success() { return TaskStatus.success(getId()); @@ -186,14 +157,6 @@ public abstract class AbstractTask implements Task protected Iterable getTaskLocks(TaskToolbox toolbox) throws IOException { - final List locks = toolbox.getTaskActionClient().submit(new LockListAction()); - - if (locks.isEmpty() && getImplicitLockInterval().isPresent()) { - // In the Peon's local mode, the implicit lock interval is not pre-acquired, so we need to try it here. - toolbox.getTaskActionClient().submit(new LockAcquireAction(getImplicitLockInterval().get())); - return toolbox.getTaskActionClient().submit(new LockListAction()); - } else { - return locks; - } + return toolbox.getTaskActionClient().submit(new LockListAction()); } } diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/DeleteTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/DeleteTask.java index 32d3e49e618..8f5a0da8f46 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/DeleteTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/DeleteTask.java @@ -30,8 +30,9 @@ import io.druid.granularity.QueryGranularity; import io.druid.indexing.common.TaskLock; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.TaskToolbox; -import io.druid.indexing.common.actions.LockListAction; +import io.druid.indexing.common.actions.LockTryAcquireAction; import io.druid.indexing.common.actions.SegmentInsertAction; +import io.druid.indexing.common.actions.TaskActionClient; import io.druid.query.aggregation.AggregatorFactory; import io.druid.segment.IndexMerger; import io.druid.segment.IndexableAdapter; @@ -44,7 +45,7 @@ import org.joda.time.Interval; import java.io.File; -public class DeleteTask extends AbstractTask +public class DeleteTask extends AbstractFixedIntervalTask { private static final Logger log = new Logger(DeleteTask.class); @@ -78,8 +79,7 @@ public class DeleteTask extends AbstractTask public TaskStatus run(TaskToolbox toolbox) throws Exception { // Strategy: Create an empty segment covering the interval to be deleted - final TaskLock myLock = Iterables.getOnlyElement(getTaskLocks(toolbox)); - final Interval interval = this.getImplicitLockInterval().get(); + final TaskLock myLock = Iterables.getOnlyElement(getTaskLocks(toolbox)); final IncrementalIndex empty = new IncrementalIndex(0, QueryGranularity.NONE, new AggregatorFactory[0]); final IndexableAdapter emptyAdapter = new IncrementalIndexAdapter(interval, empty); diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/HadoopIndexTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/HadoopIndexTask.java index e687875433b..400b088a693 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/HadoopIndexTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/HadoopIndexTask.java @@ -37,12 +37,15 @@ import io.druid.indexer.HadoopDruidIndexerSchema; import io.druid.indexing.common.TaskLock; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.TaskToolbox; +import io.druid.indexing.common.actions.LockTryAcquireAction; import io.druid.indexing.common.actions.SegmentInsertAction; +import io.druid.indexing.common.actions.TaskActionClient; import io.druid.initialization.Initialization; import io.druid.server.initialization.ExtensionsConfig; import io.druid.timeline.DataSegment; import io.tesla.aether.internal.DefaultTeslaAether; import org.joda.time.DateTime; +import org.joda.time.Interval; import java.io.File; import java.lang.reflect.Method; @@ -51,7 +54,7 @@ import java.net.URLClassLoader; import java.util.Arrays; import java.util.List; -public class HadoopIndexTask extends AbstractTask +public class HadoopIndexTask extends AbstractFixedIntervalTask { private static final Logger log = new Logger(HadoopIndexTask.class); private static String defaultHadoopCoordinates = "org.apache.hadoop:hadoop-core:1.0.3"; @@ -88,10 +91,14 @@ public class HadoopIndexTask extends AbstractTask super( id != null ? id : String.format("index_hadoop_%s_%s", schema.getDataSource(), new DateTime()), schema.getDataSource(), - JodaUtils.umbrellaInterval(JodaUtils.condenseIntervals(schema.getGranularitySpec().bucketIntervals())) + JodaUtils.umbrellaInterval( + JodaUtils.condenseIntervals( + schema.getGranularitySpec() + .bucketIntervals() + ) + ) ); - // Some HadoopDruidIndexerSchema stuff doesn't make sense in the context of the indexing service Preconditions.checkArgument(schema.getSegmentOutputPath() == null, "segmentOutputPath must be absent"); Preconditions.checkArgument(schema.getWorkingPath() == null, "workingPath must be absent"); @@ -107,7 +114,6 @@ public class HadoopIndexTask extends AbstractTask return "index_hadoop"; } - @JsonProperty("config") public HadoopDruidIndexerSchema getSchema() { diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/IndexDeterminePartitionsTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/IndexDeterminePartitionsTask.java deleted file mode 100644 index a89cd475ff0..00000000000 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/IndexDeterminePartitionsTask.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Druid - a distributed column store. - * Copyright (C) 2012, 2013 Metamarkets Group Inc. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package io.druid.indexing.common.task; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.base.Function; -import com.google.common.base.Preconditions; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Ordering; -import com.google.common.collect.Sets; -import com.google.common.collect.TreeMultiset; -import com.google.common.primitives.Ints; -import com.metamx.common.logger.Logger; -import io.druid.data.input.Firehose; -import io.druid.data.input.FirehoseFactory; -import io.druid.data.input.InputRow; -import io.druid.indexing.common.TaskStatus; -import io.druid.indexing.common.TaskToolbox; -import io.druid.indexing.common.actions.SpawnTasksAction; -import io.druid.segment.realtime.Schema; -import io.druid.timeline.partition.NoneShardSpec; -import io.druid.timeline.partition.ShardSpec; -import io.druid.timeline.partition.SingleDimensionShardSpec; -import org.joda.time.DateTime; -import org.joda.time.Interval; - -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class IndexDeterminePartitionsTask extends AbstractTask -{ - private static String makeTaskId(String groupId, DateTime start, DateTime end) - { - return String.format( - "%s_partitions_%s_%s", - groupId, - start, - end - ); - } - - @JsonIgnore - private final FirehoseFactory firehoseFactory; - - @JsonIgnore - private final Schema schema; - - @JsonIgnore - private final long targetPartitionSize; - - @JsonIgnore - private final int rowFlushBoundary; - - private static final Logger log = new Logger(IndexTask.class); - - @JsonCreator - public IndexDeterminePartitionsTask( - @JsonProperty("id") String id, - @JsonProperty("groupId") String groupId, - @JsonProperty("interval") Interval interval, - @JsonProperty("firehose") FirehoseFactory firehoseFactory, - @JsonProperty("schema") Schema schema, - @JsonProperty("targetPartitionSize") long targetPartitionSize, - @JsonProperty("rowFlushBoundary") int rowFlushBoundary - ) - { - super( - id != null ? id : makeTaskId(groupId, interval.getStart(), interval.getEnd()), - groupId, - schema.getDataSource(), - Preconditions.checkNotNull(interval, "interval") - ); - - this.firehoseFactory = firehoseFactory; - this.schema = schema; - this.targetPartitionSize = targetPartitionSize; - this.rowFlushBoundary = rowFlushBoundary; - } - - @Override - public String getType() - { - return "index_partitions"; - } - - @Override - public TaskStatus run(TaskToolbox toolbox) throws Exception - { - log.info("Running with targetPartitionSize[%d]", targetPartitionSize); - - // The implementation of this determine partitions stuff is less than optimal. Should be done better. - - // We know this exists - final Interval interval = getImplicitLockInterval().get(); - - // Blacklist dimensions that have multiple values per row - final Set unusableDimensions = Sets.newHashSet(); - - // Track values of all non-blacklisted dimensions - final Map> dimensionValueMultisets = Maps.newHashMap(); - - // Load data - final Firehose firehose = firehoseFactory.connect(); - - try { - while (firehose.hasMore()) { - - final InputRow inputRow = firehose.nextRow(); - - if (interval.contains(inputRow.getTimestampFromEpoch())) { - - // Extract dimensions from event - for (final String dim : inputRow.getDimensions()) { - final List dimValues = inputRow.getDimension(dim); - - if (!unusableDimensions.contains(dim)) { - - if (dimValues.size() == 1) { - - // Track this value - TreeMultiset dimensionValueMultiset = dimensionValueMultisets.get(dim); - - if (dimensionValueMultiset == null) { - dimensionValueMultiset = TreeMultiset.create(); - dimensionValueMultisets.put(dim, dimensionValueMultiset); - } - - dimensionValueMultiset.add(dimValues.get(0)); - - } else { - - // Only single-valued dimensions can be used for partitions - unusableDimensions.add(dim); - dimensionValueMultisets.remove(dim); - - } - - } - } - - } - - } - } - finally { - firehose.close(); - } - - // ShardSpecs for index generator tasks - final List shardSpecs = Lists.newArrayList(); - - // Select highest-cardinality dimension - Ordering>> byCardinalityOrdering = new Ordering>>() - { - @Override - public int compare( - Map.Entry> left, - Map.Entry> right - ) - { - return Ints.compare(left.getValue().elementSet().size(), right.getValue().elementSet().size()); - } - }; - - if (dimensionValueMultisets.isEmpty()) { - // No suitable partition dimension. We'll make one big segment and hope for the best. - log.info("No suitable partition dimension found"); - shardSpecs.add(new NoneShardSpec()); - } else { - // Find best partition dimension (heuristic: highest cardinality). - final Map.Entry> partitionEntry = - byCardinalityOrdering.max(dimensionValueMultisets.entrySet()); - - final String partitionDim = partitionEntry.getKey(); - final TreeMultiset partitionDimValues = partitionEntry.getValue(); - - log.info( - "Partitioning on dimension[%s] with cardinality[%d] over rows[%d]", - partitionDim, - partitionDimValues.elementSet().size(), - partitionDimValues.size() - ); - - // Iterate over unique partition dimension values in sorted order - String currentPartitionStart = null; - int currentPartitionSize = 0; - for (final String partitionDimValue : partitionDimValues.elementSet()) { - currentPartitionSize += partitionDimValues.count(partitionDimValue); - if (currentPartitionSize >= targetPartitionSize) { - final ShardSpec shardSpec = new SingleDimensionShardSpec( - partitionDim, - currentPartitionStart, - partitionDimValue, - shardSpecs.size() - ); - - log.info("Adding shard: %s", shardSpec); - shardSpecs.add(shardSpec); - - currentPartitionSize = partitionDimValues.count(partitionDimValue); - currentPartitionStart = partitionDimValue; - } - } - - if (currentPartitionSize > 0) { - // One last shard to go - final ShardSpec shardSpec; - - if (shardSpecs.isEmpty()) { - shardSpec = new NoneShardSpec(); - } else { - shardSpec = new SingleDimensionShardSpec( - partitionDim, - currentPartitionStart, - null, - shardSpecs.size() - ); - } - - log.info("Adding shard: %s", shardSpec); - shardSpecs.add(shardSpec); - } - } - - List nextTasks = Lists.transform( - shardSpecs, - new Function() - { - @Override - public Task apply(ShardSpec shardSpec) - { - return new IndexGeneratorTask( - null, - getGroupId(), - getImplicitLockInterval().get(), - firehoseFactory, - new Schema( - schema.getDataSource(), - schema.getSpatialDimensions(), - schema.getAggregators(), - schema.getIndexGranularity(), - shardSpec - ), - rowFlushBoundary - ); - } - } - ); - - toolbox.getTaskActionClient().submit(new SpawnTasksAction(nextTasks)); - - return TaskStatus.success(getId()); - } - - @JsonProperty - public FirehoseFactory getFirehoseFactory() - { - return firehoseFactory; - } - - @JsonProperty - public Schema getSchema() - { - return schema; - } - - @JsonProperty - public long getTargetPartitionSize() - { - return targetPartitionSize; - } - - @JsonProperty - public int getRowFlushBoundary() - { - return rowFlushBoundary; - } -} diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/IndexGeneratorTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/IndexGeneratorTask.java deleted file mode 100644 index 84f6211ad52..00000000000 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/IndexGeneratorTask.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Druid - a distributed column store. - * Copyright (C) 2012, 2013 Metamarkets Group Inc. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package io.druid.indexing.common.task; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import com.metamx.common.logger.Logger; -import io.druid.data.input.Firehose; -import io.druid.data.input.FirehoseFactory; -import io.druid.data.input.InputRow; -import io.druid.indexing.common.TaskLock; -import io.druid.indexing.common.TaskStatus; -import io.druid.indexing.common.TaskToolbox; -import io.druid.indexing.common.actions.SegmentInsertAction; -import io.druid.indexing.common.index.YeOldePlumberSchool; -import io.druid.segment.loading.DataSegmentPusher; -import io.druid.segment.realtime.FireDepartmentMetrics; -import io.druid.segment.realtime.Schema; -import io.druid.segment.realtime.plumber.Plumber; -import io.druid.segment.realtime.plumber.Sink; -import io.druid.timeline.DataSegment; -import org.joda.time.DateTime; -import org.joda.time.Interval; - -import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -public class IndexGeneratorTask extends AbstractTask -{ - @JsonIgnore - private final FirehoseFactory firehoseFactory; - - @JsonIgnore - private final Schema schema; - - @JsonIgnore - private final int rowFlushBoundary; - - private static final Logger log = new Logger(IndexTask.class); - - @JsonCreator - public IndexGeneratorTask( - @JsonProperty("id") String id, - @JsonProperty("groupId") String groupId, - @JsonProperty("interval") Interval interval, - @JsonProperty("firehose") FirehoseFactory firehoseFactory, - @JsonProperty("schema") Schema schema, - @JsonProperty("rowFlushBoundary") int rowFlushBoundary - ) - { - super( - id != null - ? id - : String.format( - "%s_generator_%s_%s_%s", - groupId, - interval.getStart(), - interval.getEnd(), - schema.getShardSpec().getPartitionNum() - ), - groupId, - schema.getDataSource(), - Preconditions.checkNotNull(interval, "interval") - ); - - this.firehoseFactory = firehoseFactory; - this.schema = schema; - this.rowFlushBoundary = rowFlushBoundary; - } - - @Override - public String getType() - { - return "index_generator"; - } - - @Override - public TaskStatus run(final TaskToolbox toolbox) throws Exception - { - // We should have a lock from before we started running - final TaskLock myLock = Iterables.getOnlyElement(getTaskLocks(toolbox)); - - // We know this exists - final Interval interval = getImplicitLockInterval().get(); - - // Set up temporary directory for indexing - final File tmpDir = new File( - toolbox.getTaskWorkDir(), - String.format( - "%s_%s_%s_%s_%s", - this.getDataSource(), - interval.getStart(), - interval.getEnd(), - myLock.getVersion(), - schema.getShardSpec().getPartitionNum() - ) - ); - - // We need to track published segments. - final List pushedSegments = new CopyOnWriteArrayList(); - final DataSegmentPusher wrappedDataSegmentPusher = new DataSegmentPusher() - { - @Override - public String getPathForHadoop(String dataSource) - { - return toolbox.getSegmentPusher().getPathForHadoop(dataSource); - } - - @Override - public DataSegment push(File file, DataSegment segment) throws IOException - { - final DataSegment pushedSegment = toolbox.getSegmentPusher().push(file, segment); - pushedSegments.add(pushedSegment); - return pushedSegment; - } - }; - - // Create firehose + plumber - final FireDepartmentMetrics metrics = new FireDepartmentMetrics(); - final Firehose firehose = firehoseFactory.connect(); - final Plumber plumber = new YeOldePlumberSchool( - interval, - myLock.getVersion(), - wrappedDataSegmentPusher, - tmpDir - ).findPlumber(schema, metrics); - - // rowFlushBoundary for this job - final int myRowFlushBoundary = this.rowFlushBoundary > 0 - ? rowFlushBoundary - : toolbox.getConfig().getDefaultRowFlushBoundary(); - - try { - while (firehose.hasMore()) { - final InputRow inputRow = firehose.nextRow(); - - if (shouldIndex(inputRow)) { - final Sink sink = plumber.getSink(inputRow.getTimestampFromEpoch()); - if (sink == null) { - throw new NullPointerException( - String.format( - "Was expecting non-null sink for timestamp[%s]", - new DateTime(inputRow.getTimestampFromEpoch()) - ) - ); - } - - int numRows = sink.add(inputRow); - metrics.incrementProcessed(); - - if (numRows >= myRowFlushBoundary) { - plumber.persist(firehose.commit()); - } - } else { - metrics.incrementThrownAway(); - } - } - } - finally { - firehose.close(); - } - - plumber.persist(firehose.commit()); - plumber.finishJob(); - - // Output metrics - log.info( - "Task[%s] took in %,d rows (%,d processed, %,d unparseable, %,d thrown away) and output %,d rows", - getId(), - metrics.processed() + metrics.unparseable() + metrics.thrownAway(), - metrics.processed(), - metrics.unparseable(), - metrics.thrownAway(), - metrics.rowOutput() - ); - - // Request segment pushes - toolbox.getTaskActionClient().submit(new SegmentInsertAction(ImmutableSet.copyOf(pushedSegments))); - - // Done - return TaskStatus.success(getId()); - } - - /** - * Should we index this inputRow? Decision is based on our interval and shardSpec. - * - * @param inputRow the row to check - * - * @return true or false - */ - private boolean shouldIndex(InputRow inputRow) - { - if (getImplicitLockInterval().get().contains(inputRow.getTimestampFromEpoch())) { - return schema.getShardSpec().isInChunk(inputRow); - } else { - return false; - } - } - - @JsonProperty("firehose") - public FirehoseFactory getFirehoseFactory() - { - return firehoseFactory; - } - - @JsonProperty - public Schema getSchema() - { - return schema; - } - - @JsonProperty - public int getRowFlushBoundary() - { - return rowFlushBoundary; - } -} diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java index 6e7d9f61f17..a92f893ccd6 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java @@ -22,26 +22,47 @@ package io.druid.indexing.common.task; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.api.client.util.Sets; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Ordering; +import com.google.common.collect.TreeMultiset; +import com.google.common.primitives.Ints; import com.metamx.common.logger.Logger; +import io.druid.data.input.Firehose; import io.druid.data.input.FirehoseFactory; +import io.druid.data.input.InputRow; import io.druid.data.input.impl.SpatialDimensionSchema; import io.druid.granularity.QueryGranularity; import io.druid.indexer.granularity.GranularitySpec; +import io.druid.indexing.common.TaskLock; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.TaskToolbox; -import io.druid.indexing.common.actions.SpawnTasksAction; -import io.druid.indexing.common.actions.TaskActionClient; +import io.druid.indexing.common.index.YeOldePlumberSchool; import io.druid.query.aggregation.AggregatorFactory; +import io.druid.segment.loading.DataSegmentPusher; +import io.druid.segment.realtime.FireDepartmentMetrics; import io.druid.segment.realtime.Schema; +import io.druid.segment.realtime.plumber.Plumber; +import io.druid.segment.realtime.plumber.Sink; +import io.druid.timeline.DataSegment; import io.druid.timeline.partition.NoneShardSpec; +import io.druid.timeline.partition.ShardSpec; +import io.druid.timeline.partition.SingleDimensionShardSpec; import org.joda.time.DateTime; import org.joda.time.Interval; +import java.io.File; +import java.io.IOException; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; -public class IndexTask extends AbstractTask +public class IndexTask extends AbstractFixedIntervalTask { private static final Logger log = new Logger(IndexTask.class); @@ -58,7 +79,7 @@ public class IndexTask extends AbstractTask private final QueryGranularity indexGranularity; @JsonIgnore - private final long targetPartitionSize; + private final int targetPartitionSize; @JsonIgnore private final FirehoseFactory firehoseFactory; @@ -74,7 +95,7 @@ public class IndexTask extends AbstractTask @JsonProperty("spatialDimensions") List spatialDimensions, @JsonProperty("aggregators") AggregatorFactory[] aggregators, @JsonProperty("indexGranularity") QueryGranularity indexGranularity, - @JsonProperty("targetPartitionSize") long targetPartitionSize, + @JsonProperty("targetPartitionSize") int targetPartitionSize, @JsonProperty("firehose") FirehoseFactory firehoseFactory, @JsonProperty("rowFlushBoundary") int rowFlushBoundary ) @@ -96,58 +117,10 @@ public class IndexTask extends AbstractTask this.aggregators = aggregators; this.indexGranularity = (indexGranularity == null) ? QueryGranularity.NONE : indexGranularity; this.targetPartitionSize = targetPartitionSize; - this.firehoseFactory = firehoseFactory; + this.firehoseFactory = Preconditions.checkNotNull(firehoseFactory, "firehoseFactory"); this.rowFlushBoundary = rowFlushBoundary; } - public List toSubtasks() - { - final List retVal = Lists.newArrayList(); - - for (final Interval interval : granularitySpec.bucketIntervals()) { - if (targetPartitionSize > 0) { - // Need to do one pass over the data before indexing in order to determine good partitions - retVal.add( - new IndexDeterminePartitionsTask( - null, - getGroupId(), - interval, - firehoseFactory, - new Schema( - getDataSource(), - spatialDimensions, - aggregators, - indexGranularity, - new NoneShardSpec() - ), - targetPartitionSize, - rowFlushBoundary - ) - ); - } else { - // Jump straight into indexing - retVal.add( - new IndexGeneratorTask( - null, - getGroupId(), - interval, - firehoseFactory, - new Schema( - getDataSource(), - spatialDimensions, - aggregators, - indexGranularity, - new NoneShardSpec() - ), - rowFlushBoundary - ) - ); - } - } - - return retVal; - } - @Override public String getType() { @@ -155,16 +128,282 @@ public class IndexTask extends AbstractTask } @Override - public TaskStatus preflight(TaskActionClient taskActionClient) throws Exception + public TaskStatus run(TaskToolbox toolbox) throws Exception { - taskActionClient.submit(new SpawnTasksAction(toSubtasks())); + final TaskLock myLock = Iterables.getOnlyElement(getTaskLocks(toolbox)); + final Set segments = Sets.newHashSet(); + for (final Interval bucket : granularitySpec.bucketIntervals()) { + final List shardSpecs; + if (targetPartitionSize > 0) { + shardSpecs = determinePartitions( + toolbox, + new Schema( + getDataSource(), + spatialDimensions, + aggregators, + indexGranularity, + new NoneShardSpec() // Dummy shardSpec, won't be used + ), + bucket, + targetPartitionSize + ); + } else { + shardSpecs = ImmutableList.of(new NoneShardSpec()); + } + for (final ShardSpec shardSpec : shardSpecs) { + final DataSegment segment = generateSegment( + toolbox, + new Schema( + getDataSource(), + spatialDimensions, + aggregators, + indexGranularity, + shardSpec + ), + interval, + myLock.getVersion() + ); + segments.add(segment); + } + } return TaskStatus.success(getId()); } - @Override - public TaskStatus run(TaskToolbox toolbox) throws Exception + private List determinePartitions( + final TaskToolbox toolbox, + final Schema schema, + final Interval interval, + final int targetPartitionSize + ) throws IOException { - throw new IllegalStateException("IndexTasks should not be run!"); + log.info("Determining partitions for interval[%s] with targetPartitionSize[%d]", interval, targetPartitionSize); + + // The implementation of this determine partitions stuff is less than optimal. Should be done better. + + // Blacklist dimensions that have multiple values per row + final Set unusableDimensions = com.google.common.collect.Sets.newHashSet(); + // Track values of all non-blacklisted dimensions + final Map> dimensionValueMultisets = Maps.newHashMap(); + + // Load data + try (Firehose firehose = firehoseFactory.connect()) { + while (firehose.hasMore()) { + final InputRow inputRow = firehose.nextRow(); + if (interval.contains(inputRow.getTimestampFromEpoch())) { + // Extract dimensions from event + for (final String dim : inputRow.getDimensions()) { + final List dimValues = inputRow.getDimension(dim); + if (!unusableDimensions.contains(dim)) { + if (dimValues.size() == 1) { + // Track this value + TreeMultiset dimensionValueMultiset = dimensionValueMultisets.get(dim); + if (dimensionValueMultiset == null) { + dimensionValueMultiset = TreeMultiset.create(); + dimensionValueMultisets.put(dim, dimensionValueMultiset); + } + dimensionValueMultiset.add(dimValues.get(0)); + } else { + // Only single-valued dimensions can be used for partitions + unusableDimensions.add(dim); + dimensionValueMultisets.remove(dim); + } + } + } + } + } + } + + // ShardSpecs we will return + final List shardSpecs = Lists.newArrayList(); + + // Select highest-cardinality dimension + Ordering>> byCardinalityOrdering = new Ordering>>() + { + @Override + public int compare( + Map.Entry> left, + Map.Entry> right + ) + { + return Ints.compare(left.getValue().elementSet().size(), right.getValue().elementSet().size()); + } + }; + + if (dimensionValueMultisets.isEmpty()) { + // No suitable partition dimension. We'll make one big segment and hope for the best. + log.info("No suitable partition dimension found"); + shardSpecs.add(new NoneShardSpec()); + } else { + // Find best partition dimension (heuristic: highest cardinality). + final Map.Entry> partitionEntry = + byCardinalityOrdering.max(dimensionValueMultisets.entrySet()); + + final String partitionDim = partitionEntry.getKey(); + final TreeMultiset partitionDimValues = partitionEntry.getValue(); + + log.info( + "Partitioning on dimension[%s] with cardinality[%d] over rows[%d]", + partitionDim, + partitionDimValues.elementSet().size(), + partitionDimValues.size() + ); + + // Iterate over unique partition dimension values in sorted order + String currentPartitionStart = null; + int currentPartitionSize = 0; + for (final String partitionDimValue : partitionDimValues.elementSet()) { + currentPartitionSize += partitionDimValues.count(partitionDimValue); + if (currentPartitionSize >= targetPartitionSize) { + final ShardSpec shardSpec = new SingleDimensionShardSpec( + partitionDim, + currentPartitionStart, + partitionDimValue, + shardSpecs.size() + ); + + log.info("Adding shard: %s", shardSpec); + shardSpecs.add(shardSpec); + + currentPartitionSize = partitionDimValues.count(partitionDimValue); + currentPartitionStart = partitionDimValue; + } + } + + if (currentPartitionSize > 0) { + // One last shard to go + final ShardSpec shardSpec; + + if (shardSpecs.isEmpty()) { + shardSpec = new NoneShardSpec(); + } else { + shardSpec = new SingleDimensionShardSpec( + partitionDim, + currentPartitionStart, + null, + shardSpecs.size() + ); + } + + log.info("Adding shard: %s", shardSpec); + shardSpecs.add(shardSpec); + } + } + + return shardSpecs; + } + + private DataSegment generateSegment( + final TaskToolbox toolbox, + final Schema schema, + final Interval interval, + final String version + ) throws IOException + { + // Set up temporary directory. + final File tmpDir = new File( + toolbox.getTaskWorkDir(), + String.format( + "%s_%s_%s_%s_%s", + this.getDataSource(), + interval.getStart(), + interval.getEnd(), + version, + schema.getShardSpec().getPartitionNum() + ) + ); + + // We need to track published segments. + final List pushedSegments = new CopyOnWriteArrayList(); + final DataSegmentPusher wrappedDataSegmentPusher = new DataSegmentPusher() + { + @Override + public String getPathForHadoop(String dataSource) + { + return toolbox.getSegmentPusher().getPathForHadoop(dataSource); + } + + @Override + public DataSegment push(File file, DataSegment segment) throws IOException + { + final DataSegment pushedSegment = toolbox.getSegmentPusher().push(file, segment); + pushedSegments.add(pushedSegment); + return pushedSegment; + } + }; + + // Create firehose + plumber + final FireDepartmentMetrics metrics = new FireDepartmentMetrics(); + final Firehose firehose = firehoseFactory.connect(); + final Plumber plumber = new YeOldePlumberSchool( + interval, + version, + wrappedDataSegmentPusher, + tmpDir + ).findPlumber(schema, metrics); + + // rowFlushBoundary for this job + final int myRowFlushBoundary = this.rowFlushBoundary > 0 + ? rowFlushBoundary + : toolbox.getConfig().getDefaultRowFlushBoundary(); + + try { + while (firehose.hasMore()) { + final InputRow inputRow = firehose.nextRow(); + + if (shouldIndex(schema, interval, inputRow)) { + final Sink sink = plumber.getSink(inputRow.getTimestampFromEpoch()); + if (sink == null) { + throw new NullPointerException( + String.format( + "Was expecting non-null sink for timestamp[%s]", + new DateTime(inputRow.getTimestampFromEpoch()) + ) + ); + } + + int numRows = sink.add(inputRow); + metrics.incrementProcessed(); + + if (numRows >= myRowFlushBoundary) { + plumber.persist(firehose.commit()); + } + } else { + metrics.incrementThrownAway(); + } + } + } + finally { + firehose.close(); + } + + plumber.persist(firehose.commit()); + plumber.finishJob(); + + // Output metrics + log.info( + "Task[%s] took in %,d rows (%,d processed, %,d unparseable, %,d thrown away) and output %,d rows", + getId(), + metrics.processed() + metrics.unparseable() + metrics.thrownAway(), + metrics.processed(), + metrics.unparseable(), + metrics.thrownAway(), + metrics.rowOutput() + ); + + // We expect a single segment to have been created. + return Iterables.getOnlyElement(pushedSegments); + } + + /** + * Should we index this inputRow? Decision is based on our interval and shardSpec. + * + * @param inputRow the row to check + * + * @return true or false + */ + private boolean shouldIndex(final Schema schema, final Interval interval, final InputRow inputRow) + { + return interval.contains(inputRow.getTimestampFromEpoch()) && schema.getShardSpec().isInChunk(inputRow); } @JsonProperty @@ -191,7 +430,7 @@ public class IndexTask extends AbstractTask return targetPartitionSize; } - @JsonProperty + @JsonProperty("firehose") public FirehoseFactory getFirehoseFactory() { return firehoseFactory; @@ -202,4 +441,10 @@ public class IndexTask extends AbstractTask { return rowFlushBoundary; } + + @JsonProperty + public List getSpatialDimensions() + { + return spatialDimensions; + } } diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/KillTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/KillTask.java index 8f4068a5e46..ef266b65f21 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/KillTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/KillTask.java @@ -28,9 +28,10 @@ import com.metamx.common.logger.Logger; import io.druid.indexing.common.TaskLock; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.TaskToolbox; -import io.druid.indexing.common.actions.LockListAction; +import io.druid.indexing.common.actions.LockTryAcquireAction; import io.druid.indexing.common.actions.SegmentListUnusedAction; import io.druid.indexing.common.actions.SegmentNukeAction; +import io.druid.indexing.common.actions.TaskActionClient; import io.druid.timeline.DataSegment; import org.joda.time.Interval; @@ -38,7 +39,7 @@ import java.util.List; /** */ -public class KillTask extends AbstractTask +public class KillTask extends AbstractFixedIntervalTask { private static final Logger log = new Logger(KillTask.class); @@ -62,18 +63,24 @@ public class KillTask extends AbstractTask return "kill"; } + @Override + public boolean isReady(TaskActionClient taskActionClient) throws Exception + { + return taskActionClient.submit(new LockTryAcquireAction(interval)).isPresent(); + } + @Override public TaskStatus run(TaskToolbox toolbox) throws Exception { // Confirm we have a lock (will throw if there isn't exactly one element) final TaskLock myLock = Iterables.getOnlyElement(getTaskLocks(toolbox)); - if(!myLock.getDataSource().equals(getDataSource())) { + if (!myLock.getDataSource().equals(getDataSource())) { throw new ISE("WTF?! Lock dataSource[%s] != task dataSource[%s]", myLock.getDataSource(), getDataSource()); } - if(!myLock.getInterval().equals(getImplicitLockInterval().get())) { - throw new ISE("WTF?! Lock interval[%s] != task interval[%s]", myLock.getInterval(), getImplicitLockInterval().get()); + if (!myLock.getInterval().equals(interval)) { + throw new ISE("WTF?! Lock interval[%s] != task interval[%s]", myLock.getInterval(), interval); } // List unused segments @@ -82,8 +89,8 @@ public class KillTask extends AbstractTask .submit(new SegmentListUnusedAction(myLock.getDataSource(), myLock.getInterval())); // Verify none of these segments have versions > lock version - for(final DataSegment unusedSegment : unusedSegments) { - if(unusedSegment.getVersion().compareTo(myLock.getVersion()) > 0) { + for (final DataSegment unusedSegment : unusedSegments) { + if (unusedSegment.getVersion().compareTo(myLock.getVersion()) > 0) { throw new ISE( "WTF?! Unused segment[%s] has version[%s] > task version[%s]", unusedSegment.getIdentifier(), diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/MergeTaskBase.java b/indexing-service/src/main/java/io/druid/indexing/common/task/MergeTaskBase.java index 750509f9cec..4f43afccc65 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/MergeTaskBase.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/MergeTaskBase.java @@ -27,7 +27,6 @@ import com.google.common.base.Joiner; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; -import com.google.common.base.Throwables; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -41,9 +40,9 @@ import com.metamx.emitter.service.ServiceMetricEvent; import io.druid.indexing.common.TaskLock; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.TaskToolbox; -import io.druid.indexing.common.actions.LockAcquireAction; -import io.druid.indexing.common.actions.LockListAction; +import io.druid.indexing.common.actions.LockTryAcquireAction; import io.druid.indexing.common.actions.SegmentInsertAction; +import io.druid.indexing.common.actions.SegmentListUsedAction; import io.druid.indexing.common.actions.TaskActionClient; import io.druid.segment.IndexIO; import io.druid.timeline.DataSegment; @@ -60,7 +59,7 @@ import java.util.Set; /** */ -public abstract class MergeTaskBase extends AbstractTask +public abstract class MergeTaskBase extends AbstractFixedIntervalTask { @JsonIgnore private final List segments; @@ -186,9 +185,12 @@ public abstract class MergeTaskBase extends AbstractTask * we are operating on every segment that overlaps the chosen interval. */ @Override - public TaskStatus preflight(TaskActionClient taskActionClient) + public boolean isReady(TaskActionClient taskActionClient) throws Exception { - try { + // Try to acquire lock + if (!super.isReady(taskActionClient)) { + return false; + } else { final Function toIdentifier = new Function() { @Override @@ -199,7 +201,10 @@ public abstract class MergeTaskBase extends AbstractTask }; final Set current = ImmutableSet.copyOf( - Iterables.transform(taskActionClient.submit(defaultListUsedAction()), toIdentifier) + Iterables.transform( + taskActionClient.submit(new SegmentListUsedAction(getDataSource(), interval)), + toIdentifier + ) ); final Set requested = ImmutableSet.copyOf(Iterables.transform(segments, toIdentifier)); @@ -219,10 +224,7 @@ public abstract class MergeTaskBase extends AbstractTask ); } - return TaskStatus.running(getId()); - } - catch (IOException e) { - throw Throwables.propagate(e); + return true; } } @@ -241,7 +243,7 @@ public abstract class MergeTaskBase extends AbstractTask return Objects.toStringHelper(this) .add("id", getId()) .add("dataSource", getDataSource()) - .add("interval", getImplicitLockInterval()) + .add("interval", interval) .add("segments", segments) .toString(); } diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/NoopTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/NoopTask.java index c6291fdb4c9..001e1520eaa 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/NoopTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/NoopTask.java @@ -25,9 +25,8 @@ import com.metamx.common.logger.Logger; import io.druid.data.input.FirehoseFactory; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.TaskToolbox; +import io.druid.indexing.common.actions.TaskActionClient; import org.joda.time.DateTime; -import org.joda.time.Interval; -import org.joda.time.Period; /** */ @@ -42,19 +41,16 @@ public class NoopTask extends AbstractTask @JsonCreator public NoopTask( @JsonProperty("id") String id, - @JsonProperty("interval") Interval interval, @JsonProperty("runTime") int runTime, @JsonProperty("firehose") FirehoseFactory firehoseFactory ) { super( id == null ? String.format("noop_%s", new DateTime()) : id, - "none", - interval == null ? new Interval(Period.days(1), new DateTime()) : interval + "none" ); this.runTime = (runTime == 0) ? defaultRunTime : runTime; - this.firehoseFactory = firehoseFactory; } @@ -76,6 +72,12 @@ public class NoopTask extends AbstractTask return firehoseFactory; } + @Override + public boolean isReady(TaskActionClient taskActionClient) throws Exception + { + return true; + } + @Override public TaskStatus run(TaskToolbox toolbox) throws Exception { diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/RealtimeIndexTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/RealtimeIndexTask.java index bb30c351732..f28d83c1caf 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/RealtimeIndexTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/RealtimeIndexTask.java @@ -38,6 +38,7 @@ import io.druid.indexing.common.actions.LockAcquireAction; import io.druid.indexing.common.actions.LockListAction; import io.druid.indexing.common.actions.LockReleaseAction; import io.druid.indexing.common.actions.SegmentInsertAction; +import io.druid.indexing.common.actions.TaskActionClient; import io.druid.query.FinalizeResultsQueryRunner; import io.druid.query.Query; import io.druid.query.QueryRunner; @@ -130,8 +131,7 @@ public class RealtimeIndexTask extends AbstractTask ), 1 ) : taskResource, - schema.getDataSource(), - null + schema.getDataSource() ); this.schema = schema; @@ -167,6 +167,12 @@ public class RealtimeIndexTask extends AbstractTask } } + @Override + public boolean isReady(TaskActionClient taskActionClient) throws Exception + { + return true; + } + @Override public TaskStatus run(final TaskToolbox toolbox) throws Exception { diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/Task.java b/indexing-service/src/main/java/io/druid/indexing/common/task/Task.java index a3e42232865..df9abe826c8 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/Task.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/Task.java @@ -21,27 +21,22 @@ package io.druid.indexing.common.task; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.google.common.base.Optional; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.TaskToolbox; import io.druid.indexing.common.actions.TaskActionClient; import io.druid.query.Query; import io.druid.query.QueryRunner; -import org.joda.time.Interval; /** * Represents a task that can run on a worker. The general contracts surrounding Tasks are: *

*/ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @@ -51,8 +46,6 @@ import org.joda.time.Interval; @JsonSubTypes.Type(name = "delete", value = DeleteTask.class), @JsonSubTypes.Type(name = "kill", value = KillTask.class), @JsonSubTypes.Type(name = "index", value = IndexTask.class), - @JsonSubTypes.Type(name = "index_partitions", value = IndexDeterminePartitionsTask.class), - @JsonSubTypes.Type(name = "index_generator", value = IndexGeneratorTask.class), @JsonSubTypes.Type(name = "index_hadoop", value = HadoopIndexTask.class), @JsonSubTypes.Type(name = "index_realtime", value = RealtimeIndexTask.class), @JsonSubTypes.Type(name = "noop", value = NoopTask.class), @@ -96,12 +89,6 @@ public interface Task */ public String getDataSource(); - /** - * Returns implicit lock interval for this task, if any. Tasks without implicit lock intervals are not granted locks - * when started and must explicitly request them. - */ - public Optional getImplicitLockInterval(); - /** * Returns query runners for this task. If this task is not meant to answer queries over its datasource, this method * should return null. @@ -109,18 +96,17 @@ public interface Task public QueryRunner getQueryRunner(Query query); /** - * Execute preflight checks for a task. This typically runs on the coordinator, and will be run while - * holding a lock on our dataSource and implicit lock interval (if any). If this method throws an exception, the - * task should be considered a failure. + * Execute preflight actions for a task. This can be used to acquire locks, check preconditions, and so on. The + * actions must be idempotent, since this method may be executed multiple times. This typically runs on the + * coordinator. If this method throws an exception, the task should be considered a failure. * * @param taskActionClient action client for this task (not the full toolbox) * - * @return Some kind of status (runnable means continue on to a worker, non-runnable means we completed without - * using a worker). + * @return true if ready, false if not ready yet * - * @throws Exception + * @throws Exception if the task should be considered a failure */ - public TaskStatus preflight(TaskActionClient taskActionClient) throws Exception; + public boolean isReady(TaskActionClient taskActionClient) throws Exception; /** * Execute a task. This typically runs on a worker as determined by a TaskRunner, and will be run while diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/VersionConverterTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/VersionConverterTask.java index 5085bdbd2e3..4e2c4870a2f 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/VersionConverterTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/VersionConverterTask.java @@ -22,17 +22,16 @@ package io.druid.indexing.common.task; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.api.client.repackaged.com.google.common.base.Preconditions; import com.google.common.base.Function; -import com.google.common.collect.Lists; import com.google.common.collect.Sets; -import com.metamx.common.ISE; import com.metamx.common.guava.FunctionalIterable; import com.metamx.common.logger.Logger; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.TaskToolbox; +import io.druid.indexing.common.actions.LockTryAcquireAction; import io.druid.indexing.common.actions.SegmentInsertAction; import io.druid.indexing.common.actions.SegmentListUsedAction; -import io.druid.indexing.common.actions.SpawnTasksAction; import io.druid.indexing.common.actions.TaskActionClient; import io.druid.segment.IndexIO; import io.druid.segment.loading.SegmentLoadingException; @@ -48,10 +47,10 @@ import java.util.Map; /** */ -public class VersionConverterTask extends AbstractTask +public class VersionConverterTask extends AbstractFixedIntervalTask { private static final String TYPE = "version_converter"; - private static final Integer CURR_VERSION_INTEGER = new Integer(IndexIO.CURRENT_VERSION_ID); + private static final Integer CURR_VERSION_INTEGER = IndexIO.CURRENT_VERSION_ID; private static final Logger log = new Logger(VersionConverterTask.class); @@ -74,6 +73,8 @@ public class VersionConverterTask extends AbstractTask private static String makeId(String dataSource, Interval interval) { + Preconditions.checkNotNull(dataSource, "dataSource"); + Preconditions.checkNotNull(interval, "interval"); return joinId(TYPE, dataSource, interval.getStart(), interval.getEnd(), new DateTime()); } @@ -105,7 +106,6 @@ public class VersionConverterTask extends AbstractTask ) { super(id, groupId, dataSource, interval); - this.segment = segment; } @@ -125,45 +125,43 @@ public class VersionConverterTask extends AbstractTask public TaskStatus run(TaskToolbox toolbox) throws Exception { if (segment == null) { - throw new ISE("Segment was null, this should never run.", this.getClass().getSimpleName()); - } - - log.info("I'm in a subless mood."); - convertSegment(toolbox, segment); - return success(); - } - - @Override - public TaskStatus preflight(TaskActionClient taskActionClient) throws Exception - { - if (segment != null) { - return super.preflight(taskActionClient); - } - - List segments = taskActionClient.submit(defaultListUsedAction()); - - final FunctionalIterable tasks = FunctionalIterable - .create(segments) - .keep( - new Function() - { - @Override - public Task apply(DataSegment segment) + final List segments = toolbox.getTaskActionClient().submit( + new SegmentListUsedAction( + getDataSource(), + getInterval() + ) + ); + final FunctionalIterable tasks = FunctionalIterable + .create(segments) + .keep( + new Function() { - final Integer segmentVersion = segment.getBinaryVersion(); - if (!CURR_VERSION_INTEGER.equals(segmentVersion)) { - return new SubTask(getGroupId(), segment); + @Override + public Task apply(DataSegment segment) + { + final Integer segmentVersion = segment.getBinaryVersion(); + if (!CURR_VERSION_INTEGER.equals(segmentVersion)) { + return new SubTask(getGroupId(), segment); + } + + log.info("Skipping[%s], already version[%s]", segment.getIdentifier(), segmentVersion); + return null; } - - log.info("Skipping[%s], already version[%s]", segment.getIdentifier(), segmentVersion); - return null; } - } - ); + ); - taskActionClient.submit(new SpawnTasksAction(Lists.newArrayList(tasks))); - - return TaskStatus.success(getId()); + // Vestigial from a past time when this task spawned subtasks. + for (final Task subTask : tasks) { + final TaskStatus status = subTask.run(toolbox); + if (!status.isSuccess()) { + return status; + } + } + } else { + log.info("I'm in a subless mood."); + convertSegment(toolbox, segment); + } + return success(); } @Override @@ -185,7 +183,7 @@ public class VersionConverterTask extends AbstractTask return super.equals(o); } - public static class SubTask extends AbstractTask + public static class SubTask extends AbstractFixedIntervalTask { @JsonIgnore private final DataSegment segment; @@ -223,6 +221,12 @@ public class VersionConverterTask extends AbstractTask return "version_converter_sub"; } + @Override + public boolean isReady(TaskActionClient taskActionClient) throws Exception + { + return taskActionClient.submit(new LockTryAcquireAction(segment.getInterval())).isPresent(); + } + @Override public TaskStatus run(TaskToolbox toolbox) throws Exception { diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java b/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java index 92e1043c8eb..7d3ad05512e 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java @@ -23,15 +23,19 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.inject.Inject; +import com.metamx.common.RetryUtils; import com.metamx.common.lifecycle.LifecycleStart; import com.metamx.common.lifecycle.LifecycleStop; import com.metamx.emitter.EmittingLogger; +import com.mysql.jdbc.exceptions.MySQLTimeoutException; +import com.mysql.jdbc.exceptions.MySQLTransientException; import io.druid.db.DbConnector; import io.druid.db.DbTablesConfig; import io.druid.indexing.common.TaskLock; @@ -41,11 +45,18 @@ import io.druid.indexing.common.task.Task; import org.joda.time.DateTime; import org.skife.jdbi.v2.Handle; import org.skife.jdbi.v2.IDBI; +import org.skife.jdbi.v2.exceptions.CallbackFailedException; +import org.skife.jdbi.v2.exceptions.DBIException; import org.skife.jdbi.v2.exceptions.StatementException; +import org.skife.jdbi.v2.exceptions.UnableToObtainConnectionException; import org.skife.jdbi.v2.tweak.HandleCallback; +import java.sql.SQLException; +import java.sql.SQLRecoverableException; +import java.sql.SQLTransientException; import java.util.List; import java.util.Map; +import java.util.concurrent.Callable; public class DbTaskStorage implements TaskStorage { @@ -92,7 +103,7 @@ public class DbTaskStorage implements TaskStorage log.info("Inserting task %s with status: %s", task.getId(), status); try { - dbi.withHandle( + retryingHandle( new HandleCallback() { @Override @@ -134,7 +145,7 @@ public class DbTaskStorage implements TaskStorage log.info("Updating task %s to status: %s", status.getId(), status); - int updated = dbi.withHandle( + int updated = retryingHandle( new HandleCallback() { @Override @@ -162,7 +173,7 @@ public class DbTaskStorage implements TaskStorage @Override public Optional getTask(final String taskid) { - return dbi.withHandle( + return retryingHandle( new HandleCallback>() { @Override @@ -192,7 +203,7 @@ public class DbTaskStorage implements TaskStorage @Override public Optional getStatus(final String taskid) { - return dbi.withHandle( + return retryingHandle( new HandleCallback>() { @Override @@ -222,7 +233,7 @@ public class DbTaskStorage implements TaskStorage @Override public List getActiveTasks() { - return dbi.withHandle( + return retryingHandle( new HandleCallback>() { @Override @@ -231,7 +242,7 @@ public class DbTaskStorage implements TaskStorage final List> dbTasks = handle.createQuery( String.format( - "SELECT id, payload, status_payload FROM %s WHERE active = 1", + "SELECT id, payload, status_payload FROM %s WHERE active = 1 ORDER BY created_date", dbTables.getTasksTable() ) ) @@ -273,7 +284,7 @@ public class DbTaskStorage implements TaskStorage taskid ); - dbi.withHandle( + retryingHandle( new HandleCallback() { @Override @@ -308,7 +319,7 @@ public class DbTaskStorage implements TaskStorage if (taskLock.equals(taskLockToRemove)) { log.info("Deleting TaskLock with id[%d]: %s", id, taskLock); - dbi.withHandle( + retryingHandle( new HandleCallback() { @Override @@ -353,7 +364,7 @@ public class DbTaskStorage implements TaskStorage log.info("Logging action for task[%s]: %s", task.getId(), taskAction); - dbi.withHandle( + retryingHandle( new HandleCallback() { @Override @@ -376,7 +387,7 @@ public class DbTaskStorage implements TaskStorage @Override public List getAuditLogs(final String taskid) { - return dbi.withHandle( + return retryingHandle( new HandleCallback>() { @Override @@ -392,21 +403,18 @@ public class DbTaskStorage implements TaskStorage .bind("task_id", taskid) .list(); - return Lists.transform( - dbTaskLogs, new Function, TaskAction>() - { - @Override - public TaskAction apply(Map row) - { - try { - return jsonMapper.readValue((byte[]) row.get("log_payload"), TaskAction.class); - } - catch (Exception e) { - throw Throwables.propagate(e); - } + final List retList = Lists.newArrayList(); + for (final Map dbTaskLog : dbTaskLogs) { + try { + retList.add(jsonMapper.readValue((byte[]) dbTaskLog.get("log_payload"), TaskAction.class)); + } catch (Exception e) { + log.makeAlert(e, "Failed to deserialize TaskLog") + .addData("task", taskid) + .addData("logPayload", dbTaskLog) + .emit(); } } - ); + return retList; } } ); @@ -414,7 +422,7 @@ public class DbTaskStorage implements TaskStorage private Map getLocksWithIds(final String taskid) { - return dbi.withHandle( + return retryingHandle( new HandleCallback>() { @Override @@ -439,4 +447,45 @@ public class DbTaskStorage implements TaskStorage } ); } + + /** + * Retry SQL operations + */ + private T retryingHandle(final HandleCallback callback) { + final Callable call = new Callable() + { + @Override + public T call() throws Exception + { + return dbi.withHandle(callback); + } + }; + final Predicate shouldRetry = new Predicate() + { + @Override + public boolean apply(Throwable e) + { + return shouldRetryException(e); + } + }; + final int maxTries = 10; + try { + return RetryUtils.retry(call, shouldRetry, maxTries); + } catch (RuntimeException e) { + throw Throwables.propagate(e); + } catch (Exception e) { + throw new CallbackFailedException(e); + } + } + + private static boolean shouldRetryException(final Throwable e) + { + return e != null && (e instanceof SQLTransientException + || e instanceof MySQLTransientException + || e instanceof SQLRecoverableException + || e instanceof UnableToObtainConnectionException + || (e instanceof SQLException && ((SQLException) e).getErrorCode() == 1317) + || (e instanceof SQLException && shouldRetryException(e.getCause())) + || (e instanceof DBIException && shouldRetryException(e.getCause()))); + } } diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/ForkingTaskRunner.java b/indexing-service/src/main/java/io/druid/indexing/overlord/ForkingTaskRunner.java index 759149b7bd4..d95b9e57429 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/ForkingTaskRunner.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/ForkingTaskRunner.java @@ -101,12 +101,6 @@ public class ForkingTaskRunner implements TaskRunner, TaskLogStreamer this.exec = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(workerConfig.getCapacity())); } - @Override - public void bootstrap(List tasks) - { - // do nothing - } - @Override public ListenableFuture run(final Task task) { @@ -358,6 +352,14 @@ public class ForkingTaskRunner implements TaskRunner, TaskLogStreamer } } + @Override + public Collection getKnownTasks() + { + synchronized (tasks) { + return Lists.newArrayList(tasks.values()); + } + } + @Override public Collection getWorkers() { diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java b/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java index be87cef7255..4270c52b0ae 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java @@ -27,8 +27,8 @@ import com.google.common.base.Preconditions; import com.google.common.base.Stopwatch; import com.google.common.base.Supplier; import com.google.common.base.Throwables; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.io.InputSupplier; import com.google.common.primitives.Ints; @@ -113,6 +113,8 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer private final RemoteTaskRunnerWorkQueue runningTasks = new RemoteTaskRunnerWorkQueue(); // tasks that have not yet run private final RemoteTaskRunnerWorkQueue pendingTasks = new RemoteTaskRunnerWorkQueue(); + // tasks that are complete but not cleaned up yet + private final RemoteTaskRunnerWorkQueue completeTasks = new RemoteTaskRunnerWorkQueue(); private final ExecutorService runPendingTasksExec = Executors.newSingleThreadExecutor(); @@ -201,7 +203,7 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer if (!started) { return; } - + started = false; for (ZkWorker zkWorker : zkWorkers.values()) { zkWorker.close(); } @@ -210,9 +212,6 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer catch (Exception e) { throw Throwables.propagate(e); } - finally { - started = false; - } } @Override @@ -233,6 +232,13 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer return pendingTasks.values(); } + @Override + public Collection getKnownTasks() + { + // Racey, since there is a period of time during assignment when a task is neither pending nor running + return Lists.newArrayList(Iterables.concat(pendingTasks.values(), runningTasks.values(), completeTasks.values())); + } + public ZkWorker findWorkerRunningTask(String taskId) { for (ZkWorker zkWorker : zkWorkers.values()) { @@ -250,41 +256,6 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer return (zkWorker != null && zkWorker.isRunningTask(task.getId())); } - @Override - public void bootstrap(List tasks) - { - try { - if (!started) { - throw new ISE("Must start RTR first before calling bootstrap!"); - } - - Map existingTasks = Maps.newHashMap(); - for (ZkWorker zkWorker : zkWorkers.values()) { - for (String runningTask : zkWorker.getRunningTasks().keySet()) { - existingTasks.put(runningTask, zkWorker.getWorker()); - } - } - - for (Task task : tasks) { - Worker worker = existingTasks.get(task.getId()); - if (worker != null) { - log.info("Bootstrap found [%s] running on [%s].", task.getId(), worker.getHost()); - runningTasks.put( - task.getId(), - new RemoteTaskRunnerWorkItem( - task, - SettableFuture.create(), - worker - ) - ); - } - } - } - catch (Exception e) { - throw Throwables.propagate(e); - } - } - /** * A task will be run only if there is no current knowledge in the RemoteTaskRunner of the task. * @@ -302,7 +273,7 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer log.info("Task[%s] already running on %s.", task.getId(), zkWorker.getWorker().getHost()); TaskAnnouncement announcement = zkWorker.getRunningTasks().get(task.getId()); if (announcement.getTaskStatus().isComplete()) { - taskComplete(runningTask, zkWorker, task.getId(), announcement.getTaskStatus()); + taskComplete(runningTask, zkWorker, announcement.getTaskStatus()); } } @@ -330,39 +301,42 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer * @param taskId - task id to shutdown */ @Override - public void shutdown(String taskId) + public void shutdown(final String taskId) { - if (pendingTasks.containsKey(taskId)) { - pendingTasks.remove(taskId); - return; - } + if (!started) { + log.info("This TaskRunner is stopped. Ignorning shutdown command for task: %s", taskId); + } else if (pendingTasks.remove(taskId) != null) { + log.info("Removed task from pending queue: %s", taskId); + } else if (completeTasks.containsKey(taskId)) { + cleanup(completeTasks.get(taskId).getWorker().getHost(), taskId); + } else { + final ZkWorker zkWorker = findWorkerRunningTask(taskId); - final ZkWorker zkWorker = findWorkerRunningTask(taskId); - - if (zkWorker == null) { - log.info("Can't shutdown! No worker running task %s", taskId); - return; - } - - try { - final URL url = makeWorkerURL(zkWorker.getWorker(), String.format("/task/%s/shutdown", taskId)); - final StatusResponseHolder response = httpClient.post(url) - .go(RESPONSE_HANDLER) - .get(); - - log.info( - "Sent shutdown message to worker: %s, status %s, response: %s", - zkWorker.getWorker().getHost(), - response.getStatus(), - response.getContent() - ); - - if (!response.getStatus().equals(HttpResponseStatus.ACCEPTED)) { - log.error("Shutdown failed for %s! Are you sure the task was running?", taskId); + if (zkWorker == null) { + log.info("Can't shutdown! No worker running task %s", taskId); + return; + } + + try { + final URL url = makeWorkerURL(zkWorker.getWorker(), String.format("/task/%s/shutdown", taskId)); + final StatusResponseHolder response = httpClient.post(url) + .go(RESPONSE_HANDLER) + .get(); + + log.info( + "Sent shutdown message to worker: %s, status %s, response: %s", + zkWorker.getWorker().getHost(), + response.getStatus(), + response.getContent() + ); + + if (!response.getStatus().equals(HttpResponseStatus.ACCEPTED)) { + log.error("Shutdown failed for %s! Are you sure the task was running?", taskId); + } + } + catch (Exception e) { + throw Throwables.propagate(e); } - } - catch (Exception e) { - throw Throwables.propagate(e); } } @@ -457,21 +431,30 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer } /** - * Removes a task from the running queue and clears out the ZK status path of the task. + * Removes a task from the complete queue and clears out the ZK status path of the task. * * @param workerId - the worker that was previously running the task * @param taskId - the task to cleanup */ private void cleanup(final String workerId, final String taskId) { - log.info("Cleaning up [%s]", taskId); - runningTasks.remove(taskId); - final String statusPath = JOINER.join(zkPaths.getIndexerStatusPath(), workerId, taskId); - try { - cf.delete().guaranteed().forPath(statusPath); + if (!started) { + return; } - catch (Exception e) { - log.info("Tried to delete status path[%s] that didn't exist! Must've gone away already?", statusPath); + if (completeTasks.remove(taskId) == null) { + log.makeAlert("WTF?! Asked to cleanup nonexistent task") + .addData("workerId", workerId) + .addData("taskId", taskId) + .emit(); + } else { + log.info("Cleaning up task[%s] on worker[%s]", taskId, workerId); + final String statusPath = JOINER.join(zkPaths.getIndexerStatusPath(), workerId, taskId); + try { + cf.delete().guaranteed().forPath(statusPath); + } + catch (Exception e) { + log.info("Tried to delete status path[%s] that didn't exist! Must've gone away already?", statusPath); + } } } @@ -563,7 +546,7 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer config.getTaskAssignmentTimeout() ); - taskComplete(taskRunnerWorkItem, theZkWorker, task.getId(), TaskStatus.failure(task.getId())); + taskComplete(taskRunnerWorkItem, theZkWorker, TaskStatus.failure(task.getId())); break; } } @@ -629,7 +612,7 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer } if (taskStatus.isComplete()) { - taskComplete(taskRunnerWorkItem, zkWorker, taskId, taskStatus); + taskComplete(taskRunnerWorkItem, zkWorker, taskStatus); runPendingTasks(); } break; @@ -763,19 +746,22 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer private void taskComplete( RemoteTaskRunnerWorkItem taskRunnerWorkItem, ZkWorker zkWorker, - String taskId, TaskStatus taskStatus ) { + // Worker is done with this task + zkWorker.setLastCompletedTaskTime(new DateTime()); + // Move from running -> complete + if (taskRunnerWorkItem != null) { + completeTasks.put(taskStatus.getId(), taskRunnerWorkItem); + } + runningTasks.remove(taskStatus.getId()); + // Notify interested parties if (taskRunnerWorkItem != null) { final ListenableFuture result = taskRunnerWorkItem.getResult(); if (result != null) { ((SettableFuture) result).set(taskStatus); } } - - // Worker is done with this task - zkWorker.setLastCompletedTaskTime(new DateTime()); - cleanup(zkWorker.getWorker().getHost(), taskId); } } diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskLockbox.java b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskLockbox.java index 3dc024530b3..3f6708bd451 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskLockbox.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskLockbox.java @@ -23,13 +23,15 @@ import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.base.Predicate; +import com.google.common.collect.ComparisonChain; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.common.collect.Ordering; import com.google.common.collect.Sets; import com.google.inject.Inject; -import com.metamx.common.IAE; +import com.metamx.common.Pair; import com.metamx.common.guava.Comparators; import com.metamx.common.guava.FunctionalIterable; import com.metamx.emitter.EmittingLogger; @@ -73,8 +75,86 @@ public class TaskLockbox } /** - * Locks a task without removing it from the queue. Blocks until the lock is acquired. Throws an exception - * if the lock cannot be acquired. + * Wipe out our current in-memory state and resync it from our bundled {@link io.druid.indexing.overlord.TaskStorage}. + */ + public void syncFromStorage() + { + giant.lock(); + + try { + // Load stuff from taskStorage first. If this fails, we don't want to lose all our locks. + final List> storedLocks = Lists.newArrayList(); + for (final Task task : taskStorage.getActiveTasks()) { + for (final TaskLock taskLock : taskStorage.getLocks(task.getId())) { + storedLocks.add(Pair.of(task, taskLock)); + } + } + // Sort locks by version, so we add them back in the order they were acquired. + final Ordering> byVersionOrdering = new Ordering>() + { + @Override + public int compare(Pair left, Pair right) + { + // The second compare shouldn't be necessary, but, whatever. + return ComparisonChain.start() + .compare(left.rhs.getVersion(), right.rhs.getVersion()) + .compare(left.lhs.getId(), right.lhs.getId()) + .result(); + } + }; + running.clear(); + // Bookkeeping for a log message at the end + final Set uniqueTaskIds = Sets.newHashSet(); + int taskLockCount = 0; + for (final Pair taskAndLock : byVersionOrdering.sortedCopy(storedLocks)) { + final Task task = taskAndLock.lhs; + final TaskLock savedTaskLock = taskAndLock.rhs; + uniqueTaskIds.add(task.getId()); + final Optional acquiredTaskLock = tryLock( + task, + savedTaskLock.getInterval(), + Optional.of(savedTaskLock.getVersion()) + ); + if (acquiredTaskLock.isPresent() && savedTaskLock.getVersion().equals(acquiredTaskLock.get().getVersion())) { + taskLockCount ++; + log.info( + "Reacquired lock on interval[%s] version[%s] for task: %s", + savedTaskLock.getInterval(), + savedTaskLock.getVersion(), + task.getId() + ); + } else if (acquiredTaskLock.isPresent()) { + taskLockCount ++; + log.info( + "Could not reacquire lock on interval[%s] version[%s] (got version[%s] instead) for task: %s", + savedTaskLock.getInterval(), + savedTaskLock.getVersion(), + acquiredTaskLock.get().getVersion(), + task.getId() + ); + } else { + log.info( + "Could not reacquire lock on interval[%s] version[%s] for task: %s", + savedTaskLock.getInterval(), + savedTaskLock.getVersion(), + task.getId() + ); + } + } + log.info( + "Synced %,d locks for %,d tasks from storage (%,d locks ignored).", + taskLockCount, + uniqueTaskIds.size(), + storedLocks.size() - taskLockCount + ); + } finally { + giant.unlock(); + } + } + + /** + * Acquires a lock on behalf of a task. Blocks until the lock is acquired. Throws an exception if the lock + * cannot be acquired. */ public TaskLock lock(final Task task, final Interval interval) throws InterruptedException { @@ -97,7 +177,8 @@ public class TaskLockbox * Attempt to lock a task, without removing it from the queue. Equivalent to the long form of {@code tryLock} * with no preferred version. * - * @param task task to attempt to lock + * @param task task that wants a lock + * @param interval interval to lock * * @return lock version if lock was acquired, absent otherwise */ @@ -113,22 +194,17 @@ public class TaskLockbox * is only mostly guaranteed, however; we assume clock monotonicity and we assume that callers specifying * {@code preferredVersion} are doing the right thing. * - * @param task task to attempt to lock + * @param task task that wants a lock + * @param interval interval to lock * @param preferredVersion use this version string if one has not yet been assigned * * @return lock version if lock was acquired, absent otherwise */ - public Optional tryLock(final Task task, final Interval interval, final Optional preferredVersion) + private Optional tryLock(final Task task, final Interval interval, final Optional preferredVersion) { giant.lock(); try { - - if(task.getImplicitLockInterval().isPresent() && !task.getImplicitLockInterval().get().equals(interval)) { - // Task may only lock its fixed interval, if present - throw new IAE("Task must lock its fixed interval: %s", task.getId()); - } - final String dataSource = task.getDataSource(); final List foundPosses = findLockPossesForInterval(dataSource, interval); final TaskLockPosse posseToUse; @@ -184,9 +260,10 @@ public class TaskLockbox if (posseToUse.getTaskIds().add(task.getId())) { log.info("Added task[%s] to TaskLock[%s]", task.getId(), posseToUse.getTaskLock().getGroupId()); - // Best effort to update task storage facility + // Update task storage facility. If it fails, revoke the lock. try { taskStorage.addLock(task.getId(), posseToUse.getTaskLock()); + return Optional.of(posseToUse.getTaskLock()); } catch(Exception e) { log.makeAlert("Failed to persist lock in storage") .addData("task", task.getId()) @@ -194,12 +271,13 @@ public class TaskLockbox .addData("interval", posseToUse.getTaskLock().getInterval()) .addData("version", posseToUse.getTaskLock().getVersion()) .emit(); + unlock(task, interval); + return Optional.absent(); } } else { log.info("Task[%s] already present in TaskLock[%s]", task.getId(), posseToUse.getTaskLock().getGroupId()); + return Optional.of(posseToUse.getTaskLock()); } - - return Optional.of(posseToUse.getTaskLock()); } finally { giant.unlock(); @@ -271,7 +349,7 @@ public class TaskLockbox // Wake up blocking-lock waiters lockReleaseCondition.signalAll(); - // Best effort to remove lock from storage + // Remove lock from storage. If it cannot be removed, just ignore the failure. try { taskStorage.removeLock(task.getId(), taskLock); } catch(Exception e) { @@ -341,17 +419,12 @@ public class TaskLockbox try { final Iterable searchSpace; - if (task.getImplicitLockInterval().isPresent()) { - // Narrow down search using findLockPossesForInterval - searchSpace = findLockPossesForInterval(task.getDataSource(), task.getImplicitLockInterval().get()); + // Scan through all locks for this datasource + final NavigableMap dsRunning = running.get(task.getDataSource()); + if(dsRunning == null) { + searchSpace = ImmutableList.of(); } else { - // Scan through all locks for this datasource - final NavigableMap dsRunning = running.get(task.getDataSource()); - if(dsRunning == null) { - searchSpace = ImmutableList.of(); - } else { - searchSpace = dsRunning.values(); - } + searchSpace = dsRunning.values(); } return ImmutableList.copyOf( diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskMaster.java b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskMaster.java index 5d5cba4f200..b2d1eedf049 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskMaster.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskMaster.java @@ -34,7 +34,7 @@ import io.druid.guice.annotations.Self; import io.druid.indexing.common.actions.TaskActionClient; import io.druid.indexing.common.actions.TaskActionClientFactory; import io.druid.indexing.common.task.Task; -import io.druid.indexing.overlord.exec.TaskConsumer; +import io.druid.indexing.overlord.config.TaskQueueConfig; import io.druid.indexing.overlord.scaling.ResourceManagementScheduler; import io.druid.indexing.overlord.scaling.ResourceManagementSchedulerFactory; import io.druid.server.DruidNode; @@ -56,20 +56,22 @@ public class TaskMaster private final LeaderSelector leaderSelector; private final ReentrantLock giant = new ReentrantLock(); private final Condition mayBeStopped = giant.newCondition(); - private final TaskQueue taskQueue; private final TaskActionClientFactory taskActionClientFactory; - private final AtomicReference leaderLifecycleRef = new AtomicReference(null); + private final AtomicReference leaderLifecycleRef = new AtomicReference<>(null); private volatile boolean leading = false; private volatile TaskRunner taskRunner; + private volatile TaskQueue taskQueue; private volatile ResourceManagementScheduler resourceManagementScheduler; private static final EmittingLogger log = new EmittingLogger(TaskMaster.class); @Inject public TaskMaster( - final TaskQueue taskQueue, + final TaskQueueConfig taskQueueConfig, + final TaskLockbox taskLockbox, + final TaskStorage taskStorage, final TaskActionClientFactory taskActionClientFactory, @Self final DruidNode node, final ZkPathsConfig zkPaths, @@ -80,118 +82,99 @@ public class TaskMaster final ServiceEmitter emitter ) { - this.taskQueue = taskQueue; this.taskActionClientFactory = taskActionClientFactory; - this.leaderSelector = new LeaderSelector( - curator, zkPaths.getIndexerLeaderLatchPath(), new LeaderSelectorListener() - { - @Override - public void takeLeadership(CuratorFramework client) throws Exception - { - giant.lock(); + curator, + zkPaths.getIndexerLeaderLatchPath(), + new LeaderSelectorListener() + { + @Override + public void takeLeadership(CuratorFramework client) throws Exception + { + giant.lock(); - try { - log.info("By the power of Grayskull, I have the power!"); + try { + // Make sure the previous leadership cycle is really, really over. + stopLeading(); - taskRunner = runnerFactory.build(); - final TaskConsumer taskConsumer = new TaskConsumer( - taskQueue, - taskRunner, - taskActionClientFactory, - emitter - ); + // I AM THE MASTER OF THE UNIVERSE. + log.info("By the power of Grayskull, I have the power!"); + taskLockbox.syncFromStorage(); + taskRunner = runnerFactory.build(); + taskQueue = new TaskQueue( + taskQueueConfig, + taskStorage, + taskRunner, + taskActionClientFactory, + taskLockbox, + emitter + ); - // Bootstrap task queue and task lockbox (load state stuff from the database) - taskQueue.bootstrap(); - - // Sensible order to start stuff: - final Lifecycle leaderLifecycle = new Lifecycle(); - if (leaderLifecycleRef.getAndSet(leaderLifecycle) != null) { - log.makeAlert("TaskMaster set a new Lifecycle without the old one being cleared! Race condition") - .emit(); - } - - leaderLifecycle.addManagedInstance(taskRunner); - leaderLifecycle.addHandler( - new Lifecycle.Handler() - { - @Override - public void start() throws Exception - { - taskRunner.bootstrap(taskQueue.snapshot()); - } - - @Override - public void stop() - { + // Sensible order to start stuff: + final Lifecycle leaderLifecycle = new Lifecycle(); + if (leaderLifecycleRef.getAndSet(leaderLifecycle) != null) { + log.makeAlert("TaskMaster set a new Lifecycle without the old one being cleared! Race condition") + .emit(); + } + leaderLifecycle.addManagedInstance(taskRunner); + leaderLifecycle.addManagedInstance(taskQueue); + leaderLifecycle.addHandler( + new Lifecycle.Handler() + { + @Override + public void start() throws Exception + { + serviceAnnouncer.announce(node); + } + @Override + public void stop() + { + serviceAnnouncer.unannounce(node); + } + } + ); + if (taskRunner instanceof RemoteTaskRunner) { + final ScheduledExecutorFactory executorFactory = ScheduledExecutors.createFactory(leaderLifecycle); + resourceManagementScheduler = managementSchedulerFactory.build( + (RemoteTaskRunner) taskRunner, + executorFactory + ); + leaderLifecycle.addManagedInstance(resourceManagementScheduler); + } + try { + leaderLifecycle.start(); + leading = true; + while (leading && !Thread.currentThread().isInterrupted()) { + mayBeStopped.await(); } } - ); - leaderLifecycle.addManagedInstance(taskQueue); - - leaderLifecycle.addHandler( - new Lifecycle.Handler() - { - @Override - public void start() throws Exception - { - serviceAnnouncer.announce(node); - } - - @Override - public void stop() - { - serviceAnnouncer.unannounce(node); - } + catch (InterruptedException e) { + // Suppress so we can bow out gracefully } - ); - leaderLifecycle.addManagedInstance(taskConsumer); - - if (taskRunner instanceof RemoteTaskRunner) { - final ScheduledExecutorFactory executorFactory = ScheduledExecutors.createFactory(leaderLifecycle); - resourceManagementScheduler = managementSchedulerFactory.build( - (RemoteTaskRunner) taskRunner, - executorFactory - ); - leaderLifecycle.addManagedInstance(resourceManagementScheduler); - } - - try { - leaderLifecycle.start(); - leading = true; - - while (leading && !Thread.currentThread().isInterrupted()) { - mayBeStopped.await(); + finally { + log.info("Bowing out!"); + stopLeading(); + } + } + catch (Exception e) { + log.makeAlert(e, "Failed to lead").emit(); + throw Throwables.propagate(e); + } + finally { + giant.unlock(); } } - catch (InterruptedException e) { - // Suppress so we can bow out gracefully - } - finally { - log.info("Bowing out!"); - stopLeading(); - } - } - catch (Exception e) { - log.makeAlert(e, "Failed to lead").emit(); - throw Throwables.propagate(e); - } - finally { - giant.unlock(); - } - } - @Override - public void stateChanged(CuratorFramework client, ConnectionState newState) - { - if (newState == ConnectionState.LOST || newState == ConnectionState.SUSPENDED) { - // disconnected from zk. assume leadership is gone - stopLeading(); + @Override + public void stateChanged(CuratorFramework client, ConnectionState newState) + { + if (newState == ConnectionState.LOST || newState == ConnectionState.SUSPENDED) { + // disconnected from zk. assume leadership is gone + stopLeading(); + } + } } - } - } ); leaderSelector.setId(node.getHost()); diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskQueue.java b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskQueue.java index a78aef84f6e..f7b3c86874e 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskQueue.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskQueue.java @@ -19,172 +19,101 @@ package io.druid.indexing.overlord; +import com.google.api.client.util.Maps; +import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; -import com.google.common.collect.Multimap; -import com.google.common.collect.Ordering; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.inject.Inject; +import com.metamx.common.ISE; +import com.metamx.common.concurrent.ScheduledExecutors; import com.metamx.common.lifecycle.LifecycleStart; import com.metamx.common.lifecycle.LifecycleStop; import com.metamx.emitter.EmittingLogger; -import io.druid.indexing.common.TaskLock; +import com.metamx.emitter.service.ServiceEmitter; +import com.metamx.emitter.service.ServiceMetricEvent; import io.druid.indexing.common.TaskStatus; +import io.druid.indexing.common.actions.TaskActionClientFactory; import io.druid.indexing.common.task.Task; +import io.druid.indexing.overlord.config.TaskQueueConfig; +import org.joda.time.DateTime; +import org.joda.time.Duration; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** - * Interface between task producers and task consumers. + * Interface between task producers and the task runner. *

- * The queue accepts tasks from producers using {@link #add} and delivers tasks to consumers using either - * {@link #take} or {@link #poll}. Ordering is mostly-FIFO, with deviations when the natural next task would conflict - * with a currently-running task. In that case, tasks are skipped until a runnable one is found. + * This object accepts tasks from producers using {@link #add} and manages delivery of these tasks to a + * {@link TaskRunner}. Tasks will run in a mostly-FIFO order, with deviations when the natural next task is not ready + * in time (based on its {@link Task#isReady} method). *

- * To manage locking, the queue keeps track of currently-running tasks as {@link io.druid.indexing.common.TaskLock} objects. The idea is that - * only one TaskLock can be running on a particular dataSource + interval, and that TaskLock has a single version - * string that all tasks in the group must use to publish segments. Tasks in the same TaskLock may run concurrently. - *

- * For persistence, the queue saves new tasks from {@link #add} and task status updates from {@link #notify} using a - * {@link TaskStorage} obj - *

- * To support leader election of our containing system, the queue can be stopped (in which case it will not accept - * any new tasks, or hand out any more tasks, until started again). + * For persistence, we save all new tasks and task status changes using a {@link TaskStorage} object. */ public class TaskQueue { - private final List queue = Lists.newLinkedList(); + private final List tasks = Lists.newArrayList(); + private final Map> taskFutures = Maps.newHashMap(); + + private final TaskQueueConfig config; private final TaskStorage taskStorage; + private final TaskRunner taskRunner; + private final TaskActionClientFactory taskActionClientFactory; private final TaskLockbox taskLockbox; + private final ServiceEmitter emitter; + private final ReentrantLock giant = new ReentrantLock(); - private final Condition workMayBeAvailable = giant.newCondition(); + private final Condition managementMayBeNecessary = giant.newCondition(); + private final ExecutorService managerExec = Executors.newSingleThreadExecutor( + new ThreadFactoryBuilder() + .setDaemon(false) + .setNameFormat("TaskQueue-Manager").build() + ); + private final ScheduledExecutorService storageSyncExec = Executors.newSingleThreadScheduledExecutor( + new ThreadFactoryBuilder() + .setDaemon(false) + .setNameFormat("TaskQueue-StorageSync").build() + ); private volatile boolean active = false; private static final EmittingLogger log = new EmittingLogger(TaskQueue.class); @Inject - public TaskQueue(TaskStorage taskStorage, TaskLockbox taskLockbox) + public TaskQueue( + TaskQueueConfig config, + TaskStorage taskStorage, + TaskRunner taskRunner, + TaskActionClientFactory taskActionClientFactory, + TaskLockbox taskLockbox, + ServiceEmitter emitter + ) { + this.config = Preconditions.checkNotNull(config, "config"); this.taskStorage = Preconditions.checkNotNull(taskStorage, "taskStorage"); + this.taskRunner = Preconditions.checkNotNull(taskRunner, "taskRunner"); + this.taskActionClientFactory = Preconditions.checkNotNull(taskActionClientFactory, "taskActionClientFactory"); this.taskLockbox = Preconditions.checkNotNull(taskLockbox, "taskLockbox"); + this.emitter = Preconditions.checkNotNull(emitter, "emitter"); } /** - * Bootstraps this task queue and associated task lockbox. Clears the lockbox before running. Should be called - * while the queue is stopped. It is not a good idea to start the queue if this method fails. - */ - public void bootstrap() - { - // NOTE: Bootstraps can resurrect bogus stuff caused by leader races or whatevs. - - // We may want to periodically fixup the database to refer to what we think is happening, to prevent - // this from occurring and also so that bogus stuff is detected by clients in a timely manner. - - giant.lock(); - - try { - Preconditions.checkState(!active, "queue must be stopped"); - - log.info("Bootstrapping queue (and associated lockbox)"); - - queue.clear(); - taskLockbox.clear(); - - // Get all running tasks and their locks - final Multimap tasksByLock = ArrayListMultimap.create(); - - for (final Task task : taskStorage.getActiveTasks()) { - try { - final List taskLocks = taskStorage.getLocks(task.getId()); - - queue.add(task); - - for (final TaskLock taskLock : taskLocks) { - tasksByLock.put(taskLock, task); - } - } - catch (Exception e) { - log.makeAlert("Failed to bootstrap task").addData("task", task.getId()).emit(); - throw Throwables.propagate(e); - } - } - - // Sort locks by version - final Ordering> byVersionOrdering = new Ordering>() - { - @Override - public int compare(Map.Entry left, Map.Entry right) - { - return left.getKey().getVersion().compareTo(right.getKey().getVersion()); - } - }; - - // Acquire as many locks as possible, in version order - for(final Map.Entry taskAndLock : byVersionOrdering.sortedCopy(tasksByLock.entries())) { - final Task task = taskAndLock.getValue(); - final TaskLock savedTaskLock = taskAndLock.getKey(); - - final Optional acquiredTaskLock = taskLockbox.tryLock( - task, - savedTaskLock.getInterval(), - Optional.of(savedTaskLock.getVersion()) - ); - - if(acquiredTaskLock.isPresent() && savedTaskLock.getVersion().equals(acquiredTaskLock.get().getVersion())) { - log.info( - "Reacquired lock on interval[%s] version[%s] for task: %s", - savedTaskLock.getInterval(), - savedTaskLock.getVersion(), - task.getId() - ); - } else if(acquiredTaskLock.isPresent()) { - log.info( - "Could not reacquire lock on interval[%s] version[%s] (got version[%s] instead) for task: %s", - savedTaskLock.getInterval(), - savedTaskLock.getVersion(), - acquiredTaskLock.get().getVersion(), - task.getId() - ); - } else { - log.info( - "Could not reacquire lock on interval[%s] version[%s] for task: %s", - savedTaskLock.getInterval(), - savedTaskLock.getVersion(), - task.getId() - ); - } - } - - log.info("Bootstrapped %,d tasks with %,d locks. Ready to go!", queue.size(), tasksByLock.keySet().size()); - } finally { - giant.unlock(); - } - } - - /** - * Returns an immutable snapshot of the current status of this queue. - */ - public List snapshot() - { - giant.lock(); - - try { - return ImmutableList.copyOf(queue); - } finally { - giant.unlock(); - } - } - - /** - * Starts this task queue. Allows {@link #add(Task)} to accept new tasks. This should not be called on - * an already-started queue. + * Starts this task queue. Allows {@link #add(Task)} to accept new tasks. */ @LifecycleStart public void start() @@ -193,9 +122,63 @@ public class TaskQueue try { Preconditions.checkState(!active, "queue must be stopped"); - active = true; - workMayBeAvailable.signalAll(); + syncFromStorage(); + managerExec.submit( + new Runnable() + { + @Override + public void run() + { + while (true) { + try { + manage(); + break; + } + catch (InterruptedException e) { + log.info("Interrupted, exiting!"); + break; + } + catch (Exception e) { + final long restartDelay = config.getRestartDelay().getMillis(); + log.makeAlert(e, "Failed to manage").addData("restartDelay", restartDelay).emit(); + try { + Thread.sleep(restartDelay); + } + catch (InterruptedException e2) { + log.info("Interrupted, exiting!"); + break; + } + } + } + } + } + ); + ScheduledExecutors.scheduleAtFixedRate( + storageSyncExec, + config.getStorageSyncRate(), + new Callable() + { + @Override + public ScheduledExecutors.Signal call() + { + try { + syncFromStorage(); + } + catch (Exception e) { + if (active) { + log.makeAlert(e, "Failed to sync with storage").emit(); + } + } + if (active) { + return ScheduledExecutors.Signal.REPEAT; + } else { + return ScheduledExecutors.Signal.STOP; + } + } + } + ); + managementMayBeNecessary.signalAll(); } finally { giant.unlock(); @@ -203,8 +186,7 @@ public class TaskQueue } /** - * Shuts down the queue, for now. This may safely be called on an already-stopped queue. The queue may be restarted - * if desired. + * Shuts down the queue. */ @LifecycleStop public void stop() @@ -212,16 +194,99 @@ public class TaskQueue giant.lock(); try { - log.info("Naptime! Shutting down until we are started again."); - queue.clear(); - taskLockbox.clear(); + tasks.clear(); + taskFutures.clear(); active = false; + managerExec.shutdownNow(); + storageSyncExec.shutdownNow(); + managementMayBeNecessary.signalAll(); } finally { giant.unlock(); } } + /** + * Main task runner management loop. Meant to run forever, or, at least until we're stopped. + */ + private void manage() throws InterruptedException + { + log.info("Beginning management in %s.", config.getStartDelay()); + Thread.sleep(config.getStartDelay().getMillis()); + + while (active) { + giant.lock(); + + try { + // Task futures available from the taskRunner + final Map> runnerTaskFutures = Maps.newHashMap(); + for (final TaskRunnerWorkItem workItem : taskRunner.getKnownTasks()) { + runnerTaskFutures.put(workItem.getTask().getId(), workItem.getResult()); + } + // Attain futures for all active tasks (assuming they are ready to run). + for (final Task task : tasks) { + if (!taskFutures.containsKey(task.getId())) { + final ListenableFuture runnerTaskFuture; + if (runnerTaskFutures.containsKey(task.getId())) { + runnerTaskFuture = runnerTaskFutures.get(task.getId()); + } else { + // Task should be running, so run it. + final boolean taskIsReady; + try { + taskIsReady = task.isReady(taskActionClientFactory.create(task)); + } + catch (Exception e) { + log.makeAlert(e, "Exception thrown during isReady").addData("task", task.getId()).emit(); + notifyStatus(task, TaskStatus.failure(task.getId())); + continue; + } + if (taskIsReady) { + log.info("Asking taskRunner to run: %s", task.getId()); + runnerTaskFuture = taskRunner.run(task); + } else { + continue; + } + } + taskFutures.put(task.getId(), attachCallbacks(task, runnerTaskFuture)); + } + } + // Kill tasks that shouldn't be running + final Set tasksToKill = Sets.difference( + runnerTaskFutures.keySet(), + ImmutableSet.copyOf( + Lists.transform( + tasks, + new Function() + { + @Override + public String apply(Task task) + { + return task.getId(); + } + } + ) + ) + ); + if (!tasksToKill.isEmpty()) { + log.info("Asking taskRunner to clean up %,d tasks.", tasksToKill.size()); + for (final String taskId : tasksToKill) { + try { + taskRunner.shutdown(taskId); + } catch (Exception e) { + log.warn(e, "TaskRunner failed to clean up task: %s", taskId); + } + } + } + // awaitNanos because management may become necessary without this condition signalling, + // due to e.g. tasks becoming ready when other folks mess with the TaskLockbox. + managementMayBeNecessary.awaitNanos(60000000000L /* 60 seconds */); + } + finally { + giant.unlock(); + } + } + } + /** * Adds some work to the queue and the underlying task storage facility with a generic "running" status. * @@ -236,26 +301,20 @@ public class TaskQueue try { Preconditions.checkState(active, "Queue is not active!"); Preconditions.checkNotNull(task, "task"); + Preconditions.checkState(tasks.size() < config.getMaxSize(), "Too many tasks (max = %,d)", config.getMaxSize()); // If this throws with any sort of exception, including TaskExistsException, we don't want to // insert the task into our queue. try { taskStorage.insert(task, TaskStatus.running(task.getId())); - } catch (TaskExistsException e) { + } + catch (TaskExistsException e) { log.warn("Attempt to add task twice: %s", task.getId()); throw Throwables.propagate(e); } - queue.add(task); - workMayBeAvailable.signalAll(); - - // Attempt to add this task to a running task group. Silently continue if this is not possible. - // The main reason this is here is so when subtasks are added, they end up in the same task group - // as their parent whenever possible. - if(task.getImplicitLockInterval().isPresent()) { - taskLockbox.tryLock(task, task.getImplicitLockInterval().get()); - } - + tasks.add(task); + managementMayBeNecessary.signalAll(); return true; } finally { @@ -264,62 +323,22 @@ public class TaskQueue } /** - * Locks and returns next doable work from the queue. Blocks if there is no doable work. - * - * @return runnable task + * Shuts down a task if it has not yet finished. + * @param taskId task to kill */ - public Task take() throws InterruptedException + public void shutdown(final String taskId) { giant.lock(); try { - Task task; - - log.info("Waiting for work..."); - - while ((task = poll()) == null) { - // awaitNanos because work may become available without this condition signalling, - // due to other folks messing with the taskLockbox - workMayBeAvailable.awaitNanos(1000000000L /* 1 second */); - } - - return task; - } - finally { - giant.unlock(); - } - } - - /** - * Locks and removes next doable work from the queue. Returns null if there is no doable work. - * - * @return runnable task or null - */ - public Task poll() - { - giant.lock(); - - try { - for (final Task task : queue) { - if(task.getImplicitLockInterval().isPresent()) { - // If this task has a fixed interval, attempt to lock it right now. - final Optional maybeLock = taskLockbox.tryLock(task, task.getImplicitLockInterval().get()); - if(maybeLock.isPresent()) { - log.info("Task claimed with fixed interval lock: %s", task.getId()); - queue.remove(task); - return task; - } - } else { - // No fixed interval. Let's just run this and see what happens. - log.info("Task claimed with no fixed interval lock: %s", task.getId()); - queue.remove(task); - return task; + Preconditions.checkNotNull(taskId, "taskId"); + for (final Task task : tasks) { + if (task.getId().equals(taskId)) { + notifyStatus(task, TaskStatus.failure(taskId)); + break; } } - - return null; - } - finally { + } finally { giant.unlock(); } } @@ -329,14 +348,14 @@ public class TaskQueue * the task storage facility. If the status is a completed status, the task will be unlocked and no further * updates will be accepted. * - * @param task task to update + * @param task task to update * @param taskStatus new task status * * @throws NullPointerException if task or status is null * @throws IllegalArgumentException if the task ID does not match the status ID * @throws IllegalStateException if this queue is currently shut down */ - public void notify(final Task task, final TaskStatus taskStatus) + private void notifyStatus(final Task task, final TaskStatus taskStatus) { giant.lock(); @@ -350,38 +369,156 @@ public class TaskQueue task.getId(), taskStatus.getId() ); - - // Save status to DB - boolean didPersistStatus = false; + // Inform taskRunner that this task can be shut down try { - final Optional previousStatus = taskStorage.getStatus(task.getId()); - if (!previousStatus.isPresent() || !previousStatus.get().isRunnable()) { - log.makeAlert("Ignoring notification for dead task").addData("task", task.getId()).emit(); - return; - } else { - taskStorage.setStatus(taskStatus); - didPersistStatus = true; - } - } catch(Exception e) { - log.makeAlert(e, "Failed to persist status for task") - .addData("task", task.getId()) - .addData("statusCode", taskStatus.getStatusCode()) - .emit(); + taskRunner.shutdown(task.getId()); + } catch (Exception e) { + log.warn(e, "TaskRunner failed to cleanup task after completion: %s", task.getId()); } - - if(taskStatus.isComplete()) { - if(didPersistStatus) { - log.info("Task done: %s", task); - taskLockbox.unlock(task); - } else { - log.warn("Status could not be persisted! Reinserting task: %s", task.getId()); - queue.add(task); + // Remove from running tasks + int removed = 0; + for (int i = tasks.size() - 1 ; i >= 0 ; i--) { + if (tasks.get(i).getId().equals(task.getId())) { + removed ++; + tasks.remove(i); + break; + } + } + if (removed == 0) { + log.warn("Unknown task completed: %s", task.getId()); + } else if (removed > 1) { + log.makeAlert("Removed multiple copies of task").addData("count", removed).addData("task", task.getId()).emit(); + } + // Remove from futures list + taskFutures.remove(task.getId()); + if (removed > 0) { + // If we thought this task should be running, save status to DB + try { + final Optional previousStatus = taskStorage.getStatus(task.getId()); + if (!previousStatus.isPresent() || !previousStatus.get().isRunnable()) { + log.makeAlert("Ignoring notification for already-complete task").addData("task", task.getId()).emit(); + } else { + taskStorage.setStatus(taskStatus); + taskLockbox.unlock(task); + log.info("Task done: %s", task); + managementMayBeNecessary.signalAll(); + } + } + catch (Exception e) { + log.makeAlert(e, "Failed to persist status for task") + .addData("task", task.getId()) + .addData("statusCode", taskStatus.getStatusCode()) + .emit(); } - workMayBeAvailable.signalAll(); } } finally { giant.unlock(); } } + + /** + * Attach success and failure handlers to a task status future, such that when it completes, we perform the + * appropriate updates. + * + * @param statusFuture a task status future + * + * @return the same future, for convenience + */ + private ListenableFuture attachCallbacks(final Task task, final ListenableFuture statusFuture) + { + final ServiceMetricEvent.Builder metricBuilder = new ServiceMetricEvent.Builder() + .setUser2(task.getDataSource()) + .setUser4(task.getType()); + Futures.addCallback( + statusFuture, + new FutureCallback() + { + @Override + public void onSuccess(final TaskStatus status) + { + log.info("Received %s status for task: %s", status.getStatusCode(), status.getId()); + handleStatus(status); + } + + @Override + public void onFailure(final Throwable t) + { + log.makeAlert(t, "Failed to run task") + .addData("task", task.getId()) + .addData("type", task.getType()) + .addData("dataSource", task.getDataSource()) + .emit(); + handleStatus(TaskStatus.failure(task.getId())); + } + + private void handleStatus(final TaskStatus status) + { + try { + // If we're not supposed to be running anymore, don't do anything. Somewhat racey if the flag gets set + // after we check and before we commit the database transaction, but better than nothing. + if (!active) { + log.info("Abandoning task due to shutdown: %s", task.getId()); + return; + } + + notifyStatus(task, status); + + // Emit event and log, if the task is done + if (status.isComplete()) { + metricBuilder.setUser3(status.getStatusCode().toString()); + emitter.emit(metricBuilder.build("indexer/time/run/millis", status.getDuration())); + + log.info( + "Task %s: %s (%d run duration)", + status.getStatusCode(), + task, + status.getDuration() + ); + } + } + catch (Exception e) { + log.makeAlert(e, "Failed to handle task status") + .addData("task", task.getId()) + .addData("statusCode", status.getStatusCode()) + .emit(); + } + } + } + ); + return statusFuture; + } + + /** + * Resync the contents of this task queue with our storage facility. Useful to make sure our in-memory state + * corresponds to the storage facility even if the latter is manually modified. + */ + private void syncFromStorage() + { + giant.lock(); + + try { + if (active) { + final List newTasks = taskStorage.getActiveTasks(); + log.info( + "Synced %,d tasks from storage (%,d tasks added, %,d tasks removed).", + newTasks.size(), + Sets.difference(Sets.newHashSet(newTasks), Sets.newHashSet(tasks)).size(), + Sets.difference(Sets.newHashSet(tasks), Sets.newHashSet(newTasks)).size() + ); + tasks.clear(); + tasks.addAll(newTasks); + managementMayBeNecessary.signalAll(); + } else { + log.info("Not active. Skipping storage sync."); + } + } + catch (Exception e) { + log.warn(e, "Failed to sync tasks from storage!"); + throw Throwables.propagate(e); + } + finally { + giant.unlock(); + } + } } diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskRunner.java b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskRunner.java index 6509c975cdf..0b4b5e3ff89 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskRunner.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskRunner.java @@ -24,34 +24,24 @@ import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.task.Task; import java.util.Collection; -import java.util.List; /** - * Interface for handing off tasks. Used by a {@link io.druid.indexing.overlord.exec.TaskConsumer} to - * run tasks that have been locked. + * Interface for handing off tasks. Managed by a {@link io.druid.indexing.overlord.TaskQueue}. */ public interface TaskRunner { - /** - * Provide a new task runner with a list of tasks that may already be running. Will be called once shortly - * after instantiation and before any calls to {@link #run}. Bootstrapping should not be construed as a command - * to run the tasks; they will be passed to {@link #run} one-by-one when this is desired. Some bootstrapped tasks - * may not actually be running (for example, if they are currently held back due to not having a lock). - * - * @param tasks the tasks - */ - public void bootstrap(List tasks); - /** * Run a task. The returned status should be some kind of completed status. * * @param task task to run + * * @return task status, eventually */ public ListenableFuture run(Task task); /** - * Best-effort task shutdown. May or may not do anything. + * Inform the task runner it can clean up any resources associated with a task. This implies shutdown of any + * currently-running tasks. */ public void shutdown(String taskid); @@ -59,5 +49,7 @@ public interface TaskRunner public Collection getPendingTasks(); + public Collection getKnownTasks(); + public Collection getWorkers(); } diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorage.java b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorage.java index 3e3cbf46cdc..3a2145627df 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorage.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorage.java @@ -77,7 +77,8 @@ public interface TaskStorage public List getAuditLogs(String taskid); /** - * Returns a list of currently running or pending tasks as stored in the storage facility, in no particular order. + * Returns a list of currently running or pending tasks as stored in the storage facility. No particular order + * is guaranteed, but implementations are encouraged to return tasks in ascending order of creation. */ public List getActiveTasks(); diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorageQueryAdapter.java b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorageQueryAdapter.java index db03ab67ff7..e9a2a8d5d7c 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorageQueryAdapter.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorageQueryAdapter.java @@ -19,23 +19,14 @@ package io.druid.indexing.overlord; -import com.google.common.base.Function; import com.google.common.base.Optional; -import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.inject.Inject; -import com.metamx.common.guava.FunctionalIterable; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.actions.SegmentInsertAction; -import io.druid.indexing.common.actions.SpawnTasksAction; import io.druid.indexing.common.actions.TaskAction; -import io.druid.indexing.common.task.Task; import io.druid.timeline.DataSegment; -import java.util.List; -import java.util.Map; import java.util.Set; /** @@ -57,126 +48,20 @@ public class TaskStorageQueryAdapter } /** - * Returns all recursive task statuses for a particular task, staying within the same task group. Includes that - * task, plus any tasks it spawned, and so on. Does not include spawned tasks that ended up in a different task - * group. Does not include this task's parents or siblings. - */ - public Map> getSameGroupChildStatuses(final String taskid) - { - final Optional taskOptional = storage.getTask(taskid); - final Optional statusOptional = storage.getStatus(taskid); - final ImmutableMap.Builder> resultBuilder = ImmutableMap.builder(); - - resultBuilder.put(taskid, statusOptional); - - final Iterable nextTasks = FunctionalIterable - .create(storage.getAuditLogs(taskid)).filter( - new Predicate() - { - @Override - public boolean apply(TaskAction taskAction) - { - return taskAction instanceof SpawnTasksAction; - } - } - ).transformCat( - new Function>() - { - @Override - public Iterable apply(TaskAction taskAction) - { - return ((SpawnTasksAction) taskAction).getNewTasks(); - } - } - ); - - if(taskOptional.isPresent() && statusOptional.isPresent()) { - for(final Task nextTask : nextTasks) { - if(nextTask.getGroupId().equals(taskOptional.get().getGroupId())) { - resultBuilder.putAll(getSameGroupChildStatuses(nextTask.getId())); - } - } - } - - return resultBuilder.build(); - } - - /** - * Like {@link #getSameGroupChildStatuses}, but flattens the recursive statuses into a single, merged status. - */ - public Optional getSameGroupMergedStatus(final String taskid) - { - final Map> statuses = getSameGroupChildStatuses(taskid); - - int nSuccesses = 0; - int nFailures = 0; - int nTotal = 0; - int nPresent = 0; - - for(final Optional statusOption : statuses.values()) { - nTotal ++; - - if(statusOption.isPresent()) { - nPresent ++; - - final TaskStatus status = statusOption.get(); - - if(status.isSuccess()) { - nSuccesses ++; - } else if(status.isFailure()) { - nFailures ++; - } - } - } - - final Optional status; - - if(nPresent == 0) { - status = Optional.absent(); - } else if(nSuccesses == nTotal) { - status = Optional.of(TaskStatus.success(taskid)); - } else if(nFailures > 0) { - status = Optional.of(TaskStatus.failure(taskid)); - } else { - status = Optional.of(TaskStatus.running(taskid)); - } - - return status; - } - - /** - * Returns all segments created by descendants for a particular task that stayed within the same task group. Includes - * that task, plus any tasks it spawned, and so on. Does not include spawned tasks that ended up in a different task - * group. Does not include this task's parents or siblings. + * Returns all segments created by this task. * * This method is useful when you want to figure out all of the things a single task spawned. It does pose issues * with the result set perhaps growing boundlessly and we do not do anything to protect against that. Use at your * own risk and know that at some point, we might adjust this to actually enforce some sort of limits. */ - public Set getSameGroupNewSegments(final String taskid) + public Set getInsertedSegments(final String taskid) { - final Optional taskOptional = storage.getTask(taskid); final Set segments = Sets.newHashSet(); - final List nextTasks = Lists.newArrayList(); - - for(final TaskAction action : storage.getAuditLogs(taskid)) { - if(action instanceof SpawnTasksAction) { - nextTasks.addAll(((SpawnTasksAction) action).getNewTasks()); - } - - if(action instanceof SegmentInsertAction) { + for (final TaskAction action : storage.getAuditLogs(taskid)) { + if (action instanceof SegmentInsertAction) { segments.addAll(((SegmentInsertAction) action).getSegments()); } } - - if(taskOptional.isPresent()) { - for(final Task nextTask : nextTasks) { - if(nextTask.getGroupId().equals(taskOptional.get().getGroupId())) { - segments.addAll(getSameGroupNewSegments(nextTask.getId())); - } - } - } - return segments; } } diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/ThreadPoolTaskRunner.java b/indexing-service/src/main/java/io/druid/indexing/overlord/ThreadPoolTaskRunner.java index 78e4b9e30ec..14e3e94711a 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/ThreadPoolTaskRunner.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/ThreadPoolTaskRunner.java @@ -77,17 +77,11 @@ public class ThreadPoolTaskRunner implements TaskRunner, QuerySegmentWalker exec.shutdownNow(); } - @Override - public void bootstrap(List tasks) - { - // do nothing - } - @Override public ListenableFuture run(final Task task) { final TaskToolbox toolbox = toolboxFactory.build(task); - final ListenableFuture statusFuture = exec.submit(new ExecutorServiceTaskRunnerCallable(task, toolbox)); + final ListenableFuture statusFuture = exec.submit(new ThreadPoolTaskRunnerCallable(task, toolbox)); final TaskRunnerWorkItem taskRunnerWorkItem = new TaskRunnerWorkItem(task, statusFuture); runningItems.add(taskRunnerWorkItem); @@ -133,6 +127,12 @@ public class ThreadPoolTaskRunner implements TaskRunner, QuerySegmentWalker return ImmutableList.of(); } + @Override + public Collection getKnownTasks() + { + return ImmutableList.copyOf(runningItems); + } + @Override public Collection getWorkers() { @@ -185,12 +185,12 @@ public class ThreadPoolTaskRunner implements TaskRunner, QuerySegmentWalker return queryRunner == null ? new NoopQueryRunner() : queryRunner; } - private static class ExecutorServiceTaskRunnerCallable implements Callable + private static class ThreadPoolTaskRunnerCallable implements Callable { private final Task task; private final TaskToolbox toolbox; - public ExecutorServiceTaskRunnerCallable(Task task, TaskToolbox toolbox) + public ThreadPoolTaskRunnerCallable(Task task, TaskToolbox toolbox) { this.task = task; this.toolbox = toolbox; diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/config/IndexerDbConnectorConfig.java b/indexing-service/src/main/java/io/druid/indexing/overlord/config/IndexerDbConnectorConfig.java deleted file mode 100644 index 3318975c7ca..00000000000 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/config/IndexerDbConnectorConfig.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Druid - a distributed column store. - * Copyright (C) 2012, 2013 Metamarkets Group Inc. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package io.druid.indexing.overlord.config; - -import com.fasterxml.jackson.annotation.JsonProperty; -import io.druid.db.DbConnectorConfig; -import org.skife.config.Config; - -public abstract class IndexerDbConnectorConfig extends DbConnectorConfig -{ - @JsonProperty("taskTable") - @Config("druid.database.taskTable") - public abstract String getTaskTable(); - - @JsonProperty("taskLockTable") - @Config("druid.database.taskLockTable") - public abstract String getTaskLockTable(); - - @JsonProperty("taskLogTable") - @Config("druid.database.taskLogTable") - public abstract String getTaskLogTable(); -} diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/config/TaskQueueConfig.java b/indexing-service/src/main/java/io/druid/indexing/overlord/config/TaskQueueConfig.java new file mode 100644 index 00000000000..7d256b513db --- /dev/null +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/config/TaskQueueConfig.java @@ -0,0 +1,60 @@ +package io.druid.indexing.overlord.config; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.joda.time.Duration; +import org.joda.time.Period; + +public class TaskQueueConfig +{ + @JsonProperty + private int maxSize; + + @JsonProperty + private Duration startDelay; + + @JsonProperty + private Duration restartDelay; + + @JsonProperty + private Duration storageSyncRate; + + @JsonCreator + public TaskQueueConfig( + @JsonProperty("maxSize") final Integer maxSize, + @JsonProperty("startDelay") final Period startDelay, + @JsonProperty("restartDelay") final Period restartDelay, + @JsonProperty("storageSyncRate") final Period storageSyncRate + ) + { + this.maxSize = maxSize == null ? Integer.MAX_VALUE : maxSize; + this.startDelay = defaultDuration(startDelay, "PT1M"); + this.restartDelay = defaultDuration(restartDelay, "PT30S"); + this.storageSyncRate = defaultDuration(storageSyncRate, "PT1M"); + } + + public int getMaxSize() + { + return maxSize; + } + + public Duration getStartDelay() + { + return startDelay; + } + + public Duration getRestartDelay() + { + return restartDelay; + } + + public Duration getStorageSyncRate() + { + return storageSyncRate; + } + + private static Duration defaultDuration(final Period period, final String theDefault) + { + return (period == null ? new Period(theDefault) : period).toStandardDuration(); + } +} diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/exec/TaskConsumer.java b/indexing-service/src/main/java/io/druid/indexing/overlord/exec/TaskConsumer.java deleted file mode 100644 index d75cad14f08..00000000000 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/exec/TaskConsumer.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Druid - a distributed column store. - * Copyright (C) 2012, 2013 Metamarkets Group Inc. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package io.druid.indexing.overlord.exec; - -import com.google.common.base.Throwables; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.metamx.common.lifecycle.LifecycleStart; -import com.metamx.common.lifecycle.LifecycleStop; -import com.metamx.emitter.EmittingLogger; -import com.metamx.emitter.service.ServiceEmitter; -import com.metamx.emitter.service.ServiceMetricEvent; -import io.druid.indexing.common.TaskStatus; -import io.druid.indexing.common.actions.TaskActionClientFactory; -import io.druid.indexing.common.task.Task; -import io.druid.indexing.overlord.TaskQueue; -import io.druid.indexing.overlord.TaskRunner; - -public class TaskConsumer implements Runnable -{ - private final TaskQueue queue; - private final TaskRunner runner; - private final TaskActionClientFactory taskActionClientFactory; - private final ServiceEmitter emitter; - private final Thread thready; - - private volatile boolean shutdown = false; - - private static final EmittingLogger log = new EmittingLogger(TaskConsumer.class); - - public TaskConsumer( - TaskQueue queue, - TaskRunner runner, - TaskActionClientFactory taskActionClientFactory, - ServiceEmitter emitter - ) - { - this.queue = queue; - this.runner = runner; - this.taskActionClientFactory = taskActionClientFactory; - this.emitter = emitter; - this.thready = new Thread(this); - } - - @LifecycleStart - public void start() - { - thready.start(); - } - - @LifecycleStop - public void stop() - { - shutdown = true; - thready.interrupt(); - } - - @Override - public void run() - { - - try { - while (!Thread.currentThread().isInterrupted()) { - - final Task task; - - try { - task = queue.take(); - } - catch (InterruptedException e) { - log.info("Interrupted while waiting for new work"); - Thread.currentThread().interrupt(); - break; - } - - try { - handoff(task); - } - catch (Exception e) { - log.makeAlert(e, "Failed to hand off task") - .addData("task", task.getId()) - .addData("type", task.getType()) - .addData("dataSource", task.getDataSource()) - .addData("interval", task.getImplicitLockInterval()) - .emit(); - - // Retry would be nice, but only after we have a way to throttle and limit them. Just fail for now. - if (!shutdown) { - queue.notify(task, TaskStatus.failure(task.getId())); - } - } - } - } - catch (Exception e) { - // exit thread - log.error(e, "Uncaught exception while consuming tasks"); - throw Throwables.propagate(e); - } - } - - private void handoff(final Task task) throws Exception - { - final ServiceMetricEvent.Builder metricBuilder = new ServiceMetricEvent.Builder() - .setUser2(task.getDataSource()) - .setUser4(task.getType()) - .setUser5(task.getImplicitLockInterval().toString()); - - // Run preflight checks - TaskStatus preflightStatus; - try { - preflightStatus = task.preflight(taskActionClientFactory.create(task)); - log.info("Preflight done for task: %s", task.getId()); - } - catch (Exception e) { - preflightStatus = TaskStatus.failure(task.getId()); - log.error(e, "Exception thrown during preflight for task: %s", task.getId()); - } - - if (!preflightStatus.isRunnable()) { - log.info("Task finished during preflight: %s", task.getId()); - queue.notify(task, preflightStatus); - return; - } - - // Hand off work to TaskRunner, with a callback - final ListenableFuture status = runner.run(task); - - Futures.addCallback( - status, new FutureCallback() - { - @Override - public void onSuccess(final TaskStatus status) - { - log.info("Received %s status for task: %s", status.getStatusCode(), task); - handleStatus(status); - } - - @Override - public void onFailure(Throwable t) - { - log.makeAlert(t, "Failed to run task") - .addData("task", task.getId()) - .addData("type", task.getType()) - .addData("dataSource", task.getDataSource()) - .addData("interval", task.getImplicitLockInterval()) - .emit(); - - handleStatus(TaskStatus.failure(task.getId())); - } - - private void handleStatus(TaskStatus status) - { - try { - // If we're not supposed to be running anymore, don't do anything. Somewhat racey if the flag gets set after - // we check and before we commit the database transaction, but better than nothing. - if (shutdown) { - log.info("Abandoning task due to shutdown: %s", task.getId()); - return; - } - - queue.notify(task, status); - - // Emit event and log, if the task is done - if (status.isComplete()) { - metricBuilder.setUser3(status.getStatusCode().toString()); - emitter.emit(metricBuilder.build("indexer/time/run/millis", status.getDuration())); - - log.info( - "Task %s: %s (%d run duration)", - status.getStatusCode(), - task, - status.getDuration() - ); - } - } - catch (Exception e) { - log.makeAlert(e, "Failed to handle task status") - .addData("task", task.getId()) - .addData("statusCode", status.getStatusCode()) - .emit(); - } - } - } - ); - } -} diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/http/OldOverlordResource.java b/indexing-service/src/main/java/io/druid/indexing/overlord/http/OldOverlordResource.java deleted file mode 100644 index 6897490624a..00000000000 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/http/OldOverlordResource.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Druid - a distributed column store. - * Copyright (C) 2012, 2013 Metamarkets Group Inc. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package io.druid.indexing.overlord.http; - -import com.google.inject.Inject; -import io.druid.common.config.JacksonConfigManager; -import io.druid.indexing.overlord.TaskMaster; -import io.druid.indexing.overlord.TaskStorageQueryAdapter; -import io.druid.tasklogs.TaskLogStreamer; - -import javax.ws.rs.Path; - -/** - */ -@Deprecated -@Path("/mmx/merger/v1") -public class OldOverlordResource extends OverlordResource -{ - @Inject - public OldOverlordResource( - TaskMaster taskMaster, - TaskStorageQueryAdapter taskStorageQueryAdapter, - TaskLogStreamer taskLogStreamer, - JacksonConfigManager configManager - ) throws Exception - { - super(taskMaster, taskStorageQueryAdapter, taskLogStreamer, configManager); - } -} diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java b/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java index ef195b6f6cc..4e947e4b55d 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java @@ -72,11 +72,6 @@ public class OverlordResource return new ImmutableMap.Builder() .put("id", input.getTask().getId()) .put("dataSource", input.getTask().getDataSource()) - .put("interval", - !input.getTask().getImplicitLockInterval().isPresent() - ? "" - : input.getTask().getImplicitLockInterval().get() - ) .put("nodeType", input.getTask().getNodeType() == null ? "" : input.getTask().getNodeType()) .put("createdTime", input.getCreatedTime()) .put("queueInsertionTime", input.getQueueInsertionTime()) @@ -151,7 +146,7 @@ public class OverlordResource @Produces("application/json") public Response getTaskStatus(@PathParam("taskid") String taskid) { - return optionalTaskResponse(taskid, "status", taskStorageQueryAdapter.getSameGroupMergedStatus(taskid)); + return optionalTaskResponse(taskid, "status", taskStorageQueryAdapter.getStatus(taskid)); } @GET @@ -159,7 +154,7 @@ public class OverlordResource @Produces("application/json") public Response getTaskSegments(@PathParam("taskid") String taskid) { - final Set segments = taskStorageQueryAdapter.getSameGroupNewSegments(taskid); + final Set segments = taskStorageQueryAdapter.getInsertedSegments(taskid); return Response.ok().entity(segments).build(); } @@ -169,13 +164,13 @@ public class OverlordResource public Response doShutdown(@PathParam("taskid") final String taskid) { return asLeaderWith( - taskMaster.getTaskRunner(), - new Function() + taskMaster.getTaskQueue(), + new Function() { @Override - public Response apply(TaskRunner taskRunner) + public Response apply(TaskQueue taskQueue) { - taskRunner.shutdown(taskid); + taskQueue.shutdown(taskid); return Response.ok(ImmutableMap.of("task", taskid)).build(); } } diff --git a/indexing-service/src/test/java/io/druid/indexing/common/task/MergeTaskBaseTest.java b/indexing-service/src/test/java/io/druid/indexing/common/task/MergeTaskBaseTest.java index 577d9f4e0a6..7e8dd86f5a0 100644 --- a/indexing-service/src/test/java/io/druid/indexing/common/task/MergeTaskBaseTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/common/task/MergeTaskBaseTest.java @@ -67,7 +67,7 @@ public class MergeTaskBaseTest @Test public void testInterval() { - Assert.assertEquals(new Interval("2012-01-03/2012-01-07"), testMergeTaskBase.getImplicitLockInterval().get()); + Assert.assertEquals(new Interval("2012-01-03/2012-01-07"), testMergeTaskBase.getInterval()); } @Test diff --git a/indexing-service/src/test/java/io/druid/indexing/common/task/TaskSerdeTest.java b/indexing-service/src/test/java/io/druid/indexing/common/task/TaskSerdeTest.java index 36946e196d7..3d2598ffaa4 100644 --- a/indexing-service/src/test/java/io/druid/indexing/common/task/TaskSerdeTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/common/task/TaskSerdeTest.java @@ -20,7 +20,6 @@ package io.druid.indexing.common.task; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.metamx.common.Granularity; @@ -48,7 +47,7 @@ public class TaskSerdeTest @Test public void testIndexTaskSerde() throws Exception { - final Task task = new IndexTask( + final IndexTask task = new IndexTask( null, "foo", new UniformGranularitySpec(Granularity.DAY, ImmutableList.of(new Interval("2010-01-01/P2D"))), @@ -64,54 +63,21 @@ public class TaskSerdeTest final String json = jsonMapper.writeValueAsString(task); Thread.sleep(100); // Just want to run the clock a bit to make sure the task id doesn't change - final Task task2 = jsonMapper.readValue(json, Task.class); + final IndexTask task2 = (IndexTask) jsonMapper.readValue(json, Task.class); Assert.assertEquals("foo", task.getDataSource()); - Assert.assertEquals(Optional.of(new Interval("2010-01-01/P2D")), task.getImplicitLockInterval()); + Assert.assertEquals(new Interval("2010-01-01/P2D"), task.getInterval()); Assert.assertEquals(task.getId(), task2.getId()); Assert.assertEquals(task.getGroupId(), task2.getGroupId()); Assert.assertEquals(task.getDataSource(), task2.getDataSource()); - Assert.assertEquals(task.getImplicitLockInterval(), task2.getImplicitLockInterval()); - } - - @Test - public void testIndexGeneratorTaskSerde() throws Exception - { - final Task task = new IndexGeneratorTask( - null, - "foo", - new Interval("2010-01-01/P1D"), - null, - new Schema( - "foo", - null, - new AggregatorFactory[]{new DoubleSumAggregatorFactory("met", "met")}, - QueryGranularity.NONE, - new NoneShardSpec() - ), - -1 - ); - - final ObjectMapper jsonMapper = new DefaultObjectMapper(); - final String json = jsonMapper.writeValueAsString(task); - - Thread.sleep(100); // Just want to run the clock a bit to make sure the task id doesn't change - final Task task2 = jsonMapper.readValue(json, Task.class); - - Assert.assertEquals("foo", task.getDataSource()); - Assert.assertEquals(Optional.of(new Interval("2010-01-01/P1D")), task.getImplicitLockInterval()); - - Assert.assertEquals(task.getId(), task2.getId()); - Assert.assertEquals(task.getGroupId(), task2.getGroupId()); - Assert.assertEquals(task.getDataSource(), task2.getDataSource()); - Assert.assertEquals(task.getImplicitLockInterval(), task2.getImplicitLockInterval()); + Assert.assertEquals(task.getInterval(), task2.getInterval()); } @Test public void testMergeTaskSerde() throws Exception { - final Task task = new MergeTask( + final MergeTask task = new MergeTask( null, "foo", ImmutableList.of( @@ -126,26 +92,26 @@ public class TaskSerdeTest final String json = jsonMapper.writeValueAsString(task); Thread.sleep(100); // Just want to run the clock a bit to make sure the task id doesn't change - final Task task2 = jsonMapper.readValue(json, Task.class); + final MergeTask task2 = (MergeTask) jsonMapper.readValue(json, Task.class); Assert.assertEquals("foo", task.getDataSource()); - Assert.assertEquals(Optional.of(new Interval("2010-01-01/P1D")), task.getImplicitLockInterval()); + Assert.assertEquals(new Interval("2010-01-01/P1D"), task.getInterval()); Assert.assertEquals(task.getId(), task2.getId()); Assert.assertEquals(task.getGroupId(), task2.getGroupId()); Assert.assertEquals(task.getDataSource(), task2.getDataSource()); - Assert.assertEquals(task.getImplicitLockInterval(), task2.getImplicitLockInterval()); - Assert.assertEquals(((MergeTask) task).getSegments(), ((MergeTask) task2).getSegments()); + Assert.assertEquals(task.getInterval(), task2.getInterval()); + Assert.assertEquals(task.getSegments(), task2.getSegments()); Assert.assertEquals( - ((MergeTask) task).getAggregators().get(0).getName(), - ((MergeTask) task2).getAggregators().get(0).getName() + task.getAggregators().get(0).getName(), + task2.getAggregators().get(0).getName() ); } @Test public void testKillTaskSerde() throws Exception { - final Task task = new KillTask( + final KillTask task = new KillTask( null, "foo", new Interval("2010-01-01/P1D") @@ -155,21 +121,21 @@ public class TaskSerdeTest final String json = jsonMapper.writeValueAsString(task); Thread.sleep(100); // Just want to run the clock a bit to make sure the task id doesn't change - final Task task2 = jsonMapper.readValue(json, Task.class); + final KillTask task2 = (KillTask) jsonMapper.readValue(json, Task.class); Assert.assertEquals("foo", task.getDataSource()); - Assert.assertEquals(Optional.of(new Interval("2010-01-01/P1D")), task.getImplicitLockInterval()); + Assert.assertEquals(new Interval("2010-01-01/P1D"), task.getInterval()); Assert.assertEquals(task.getId(), task2.getId()); Assert.assertEquals(task.getGroupId(), task2.getGroupId()); Assert.assertEquals(task.getDataSource(), task2.getDataSource()); - Assert.assertEquals(task.getImplicitLockInterval(), task2.getImplicitLockInterval()); + Assert.assertEquals(task.getInterval(), task2.getInterval()); } @Test public void testVersionConverterTaskSerde() throws Exception { - final Task task = VersionConverterTask.create( + final VersionConverterTask task = VersionConverterTask.create( DataSegment.builder().dataSource("foo").interval(new Interval("2010-01-01/P1D")).version("1234").build() ); @@ -177,22 +143,22 @@ public class TaskSerdeTest final String json = jsonMapper.writeValueAsString(task); Thread.sleep(100); // Just want to run the clock a bit to make sure the task id doesn't change - final Task task2 = jsonMapper.readValue(json, Task.class); + final VersionConverterTask task2 = (VersionConverterTask) jsonMapper.readValue(json, Task.class); Assert.assertEquals("foo", task.getDataSource()); - Assert.assertEquals(Optional.of(new Interval("2010-01-01/P1D")), task.getImplicitLockInterval()); + Assert.assertEquals(new Interval("2010-01-01/P1D"), task.getInterval()); Assert.assertEquals(task.getId(), task2.getId()); Assert.assertEquals(task.getGroupId(), task2.getGroupId()); Assert.assertEquals(task.getDataSource(), task2.getDataSource()); - Assert.assertEquals(task.getImplicitLockInterval(), task2.getImplicitLockInterval()); - Assert.assertEquals(((VersionConverterTask) task).getSegment(), ((VersionConverterTask) task).getSegment()); + Assert.assertEquals(task.getInterval(), task2.getInterval()); + Assert.assertEquals(task.getSegment(), task.getSegment()); } @Test public void testVersionConverterSubTaskSerde() throws Exception { - final Task task = new VersionConverterTask.SubTask( + final VersionConverterTask.SubTask task = new VersionConverterTask.SubTask( "myGroupId", DataSegment.builder().dataSource("foo").interval(new Interval("2010-01-01/P1D")).version("1234").build() ); @@ -201,26 +167,21 @@ public class TaskSerdeTest final String json = jsonMapper.writeValueAsString(task); Thread.sleep(100); // Just want to run the clock a bit to make sure the task id doesn't change - final Task task2 = jsonMapper.readValue(json, Task.class); + final VersionConverterTask.SubTask task2 = (VersionConverterTask.SubTask) jsonMapper.readValue(json, Task.class); Assert.assertEquals("foo", task.getDataSource()); - Assert.assertEquals(Optional.of(new Interval("2010-01-01/P1D")), task.getImplicitLockInterval()); Assert.assertEquals("myGroupId", task.getGroupId()); Assert.assertEquals(task.getId(), task2.getId()); Assert.assertEquals(task.getGroupId(), task2.getGroupId()); Assert.assertEquals(task.getDataSource(), task2.getDataSource()); - Assert.assertEquals(task.getImplicitLockInterval(), task2.getImplicitLockInterval()); - Assert.assertEquals( - ((VersionConverterTask.SubTask) task).getSegment(), - ((VersionConverterTask.SubTask) task).getSegment() - ); + Assert.assertEquals(task.getSegment(), task2.getSegment()); } @Test public void testRealtimeIndexTaskSerde() throws Exception { - final Task task = new RealtimeIndexTask( + final RealtimeIndexTask task = new RealtimeIndexTask( null, new TaskResource("rofl", 2), new Schema("foo", null, new AggregatorFactory[0], QueryGranularity.NONE, new NoneShardSpec()), @@ -235,32 +196,27 @@ public class TaskSerdeTest final String json = jsonMapper.writeValueAsString(task); Thread.sleep(100); // Just want to run the clock a bit to make sure the task id doesn't change - final Task task2 = jsonMapper.readValue(json, Task.class); + final RealtimeIndexTask task2 = (RealtimeIndexTask) jsonMapper.readValue(json, Task.class); Assert.assertEquals("foo", task.getDataSource()); - Assert.assertEquals(Optional.absent(), task.getImplicitLockInterval()); Assert.assertEquals(2, task.getTaskResource().getRequiredCapacity()); Assert.assertEquals("rofl", task.getTaskResource().getAvailabilityGroup()); - Assert.assertEquals(new Period("PT10M"), ((RealtimeIndexTask) task).getWindowPeriod()); - Assert.assertEquals(IndexGranularity.HOUR, ((RealtimeIndexTask) task).getSegmentGranularity()); + Assert.assertEquals(new Period("PT10M"), task.getWindowPeriod()); + Assert.assertEquals(IndexGranularity.HOUR, task.getSegmentGranularity()); Assert.assertEquals(task.getId(), task2.getId()); Assert.assertEquals(task.getGroupId(), task2.getGroupId()); Assert.assertEquals(task.getDataSource(), task2.getDataSource()); - Assert.assertEquals(task.getImplicitLockInterval(), task2.getImplicitLockInterval()); Assert.assertEquals(task.getTaskResource().getRequiredCapacity(), task2.getTaskResource().getRequiredCapacity()); Assert.assertEquals(task.getTaskResource().getAvailabilityGroup(), task2.getTaskResource().getAvailabilityGroup()); - Assert.assertEquals(((RealtimeIndexTask) task).getWindowPeriod(), ((RealtimeIndexTask) task2).getWindowPeriod()); - Assert.assertEquals( - ((RealtimeIndexTask) task).getSegmentGranularity(), - ((RealtimeIndexTask) task2).getSegmentGranularity() - ); + Assert.assertEquals(task.getWindowPeriod(), task2.getWindowPeriod()); + Assert.assertEquals(task.getSegmentGranularity(), task2.getSegmentGranularity()); } @Test public void testDeleteTaskSerde() throws Exception { - final Task task = new DeleteTask( + final DeleteTask task = new DeleteTask( null, "foo", new Interval("2010-01-01/P1D") @@ -270,46 +226,44 @@ public class TaskSerdeTest final String json = jsonMapper.writeValueAsString(task); Thread.sleep(100); // Just want to run the clock a bit to make sure the task id doesn't change - final Task task2 = jsonMapper.readValue(json, Task.class); + final DeleteTask task2 = (DeleteTask) jsonMapper.readValue(json, Task.class); Assert.assertEquals("foo", task.getDataSource()); - Assert.assertEquals(Optional.of(new Interval("2010-01-01/P1D")), task.getImplicitLockInterval()); + Assert.assertEquals(new Interval("2010-01-01/P1D"), task.getInterval()); Assert.assertEquals(task.getId(), task2.getId()); Assert.assertEquals(task.getGroupId(), task2.getGroupId()); Assert.assertEquals(task.getDataSource(), task2.getDataSource()); - Assert.assertEquals(task.getImplicitLockInterval(), task2.getImplicitLockInterval()); - Assert.assertEquals(task.getImplicitLockInterval().get(), task2.getImplicitLockInterval().get()); + Assert.assertEquals(task.getInterval(), task2.getInterval()); } @Test public void testDeleteTaskFromJson() throws Exception { final ObjectMapper jsonMapper = new DefaultObjectMapper(); - final Task task = jsonMapper.readValue( + final DeleteTask task = (DeleteTask) jsonMapper.readValue( "{\"type\":\"delete\",\"dataSource\":\"foo\",\"interval\":\"2010-01-01/P1D\"}", Task.class ); final String json = jsonMapper.writeValueAsString(task); Thread.sleep(100); // Just want to run the clock a bit to make sure the task id doesn't change - final Task task2 = jsonMapper.readValue(json, Task.class); + final DeleteTask task2 = (DeleteTask) jsonMapper.readValue(json, Task.class); Assert.assertNotNull(task.getId()); Assert.assertEquals("foo", task.getDataSource()); - Assert.assertEquals(Optional.of(new Interval("2010-01-01/P1D")), task.getImplicitLockInterval()); + Assert.assertEquals(new Interval("2010-01-01/P1D"), task.getInterval()); Assert.assertEquals(task.getId(), task2.getId()); Assert.assertEquals(task.getGroupId(), task2.getGroupId()); Assert.assertEquals(task.getDataSource(), task2.getDataSource()); - Assert.assertEquals(task.getImplicitLockInterval(), task2.getImplicitLockInterval()); - Assert.assertEquals(task.getImplicitLockInterval().get(), task2.getImplicitLockInterval().get()); + Assert.assertEquals(task.getInterval(), task2.getInterval()); } @Test public void testAppendTaskSerde() throws Exception { - final Task task = new AppendTask( + final AppendTask task = new AppendTask( null, "foo", ImmutableList.of( @@ -321,17 +275,16 @@ public class TaskSerdeTest final String json = jsonMapper.writeValueAsString(task); Thread.sleep(100); // Just want to run the clock a bit to make sure the task id doesn't change - final Task task2 = jsonMapper.readValue(json, Task.class); + final AppendTask task2 = (AppendTask) jsonMapper.readValue(json, Task.class); Assert.assertEquals("foo", task.getDataSource()); - Assert.assertEquals(Optional.of(new Interval("2010-01-01/P1D")), task.getImplicitLockInterval()); + Assert.assertEquals(new Interval("2010-01-01/P1D"), task.getInterval()); Assert.assertEquals(task.getId(), task2.getId()); Assert.assertEquals(task.getGroupId(), task2.getGroupId()); Assert.assertEquals(task.getDataSource(), task2.getDataSource()); - Assert.assertEquals(task.getImplicitLockInterval(), task2.getImplicitLockInterval()); - Assert.assertEquals(task.getImplicitLockInterval().get(), task2.getImplicitLockInterval().get()); - Assert.assertEquals(((AppendTask) task).getSegments(), ((AppendTask) task2).getSegments()); + Assert.assertEquals(task.getInterval(), task2.getInterval()); + Assert.assertEquals(task.getSegments(), ((AppendTask) task2).getSegments()); } @Test @@ -364,14 +317,14 @@ public class TaskSerdeTest final ObjectMapper jsonMapper = new DefaultObjectMapper(); final String json = jsonMapper.writeValueAsString(task); - final Task task2 = jsonMapper.readValue(json, Task.class); + final HadoopIndexTask task2 = (HadoopIndexTask) jsonMapper.readValue(json, Task.class); Assert.assertEquals("foo", task.getDataSource()); - Assert.assertEquals(Optional.of(new Interval("2010-01-01/P1D")), task.getImplicitLockInterval()); + Assert.assertEquals(new Interval("2010-01-01/P1D"), task.getInterval()); Assert.assertEquals(task.getId(), task2.getId()); Assert.assertEquals(task.getGroupId(), task2.getGroupId()); Assert.assertEquals(task.getDataSource(), task2.getDataSource()); - Assert.assertEquals(task.getImplicitLockInterval(), task2.getImplicitLockInterval()); + Assert.assertEquals(task.getInterval(), task2.getInterval()); } } diff --git a/indexing-service/src/test/java/io/druid/indexing/overlord/RealtimeishTask.java b/indexing-service/src/test/java/io/druid/indexing/overlord/RealtimeishTask.java index b6f93f04704..e4ec6d54aa1 100644 --- a/indexing-service/src/test/java/io/druid/indexing/overlord/RealtimeishTask.java +++ b/indexing-service/src/test/java/io/druid/indexing/overlord/RealtimeishTask.java @@ -28,6 +28,7 @@ import io.druid.indexing.common.actions.LockAcquireAction; import io.druid.indexing.common.actions.LockListAction; import io.druid.indexing.common.actions.LockReleaseAction; import io.druid.indexing.common.actions.SegmentInsertAction; +import io.druid.indexing.common.actions.TaskActionClient; import io.druid.indexing.common.task.AbstractTask; import io.druid.indexing.common.task.TaskResource; import io.druid.timeline.DataSegment; @@ -42,12 +43,12 @@ public class RealtimeishTask extends AbstractTask { public RealtimeishTask() { - super("rt1", "rt", new TaskResource("rt1", 1), "foo", null); + super("rt1", "rt", new TaskResource("rt1", 1), "foo"); } - public RealtimeishTask(String id, String groupId, TaskResource taskResource, String dataSource, Interval interval) + public RealtimeishTask(String id, String groupId, TaskResource taskResource, String dataSource) { - super(id, groupId, taskResource, dataSource, interval); + super(id, groupId, taskResource, dataSource); } @Override @@ -56,6 +57,12 @@ public class RealtimeishTask extends AbstractTask return "realtime_test"; } + @Override + public boolean isReady(TaskActionClient taskActionClient) throws Exception + { + return true; + } + @Override public TaskStatus run(TaskToolbox toolbox) throws Exception { diff --git a/indexing-service/src/test/java/io/druid/indexing/overlord/RemoteTaskRunnerTest.java b/indexing-service/src/test/java/io/druid/indexing/overlord/RemoteTaskRunnerTest.java index f368e69758b..9ee11d418b4 100644 --- a/indexing-service/src/test/java/io/druid/indexing/overlord/RemoteTaskRunnerTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/overlord/RemoteTaskRunnerTest.java @@ -312,8 +312,6 @@ public class RemoteTaskRunnerTest Assert.assertTrue(existingTasks.contains("first")); Assert.assertTrue(existingTasks.contains("second")); - remoteTaskRunner.bootstrap(Arrays.asList(TestMergeTask.createDummyTask("second"))); - Set runningTasks = Sets.newHashSet( Iterables.transform( remoteTaskRunner.getRunningTasks(), @@ -343,8 +341,6 @@ public class RemoteTaskRunnerTest doSetup(); - remoteTaskRunner.bootstrap(Arrays.asList(task)); - ListenableFuture future = remoteTaskRunner.run(task); TaskStatus status = future.get(); @@ -356,7 +352,6 @@ public class RemoteTaskRunnerTest public void testWorkerRemoved() throws Exception { doSetup(); - remoteTaskRunner.bootstrap(Lists.newArrayList()); Future future = remoteTaskRunner.run(task); Assert.assertTrue(taskAnnounced(task.getId())); diff --git a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java index 0b3a4e9ed97..16d2421e761 100644 --- a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java @@ -53,12 +53,12 @@ import io.druid.indexing.common.actions.SegmentInsertAction; import io.druid.indexing.common.actions.TaskActionClientFactory; import io.druid.indexing.common.actions.TaskActionToolbox; import io.druid.indexing.common.config.TaskConfig; -import io.druid.indexing.common.task.AbstractTask; +import io.druid.indexing.common.task.AbstractFixedIntervalTask; import io.druid.indexing.common.task.IndexTask; import io.druid.indexing.common.task.KillTask; import io.druid.indexing.common.task.Task; import io.druid.indexing.common.task.TaskResource; -import io.druid.indexing.overlord.exec.TaskConsumer; +import io.druid.indexing.overlord.config.TaskQueueConfig; import io.druid.jackson.DefaultObjectMapper; import io.druid.query.aggregation.AggregatorFactory; import io.druid.query.aggregation.DoubleSumAggregatorFactory; @@ -96,7 +96,6 @@ public class TaskLifecycleTest private MockIndexerDBCoordinator mdc = null; private TaskActionClientFactory tac = null; private TaskToolboxFactory tb = null; - private TaskConsumer tc = null; TaskStorageQueryAdapter tsqa = null; private static final Ordering byIntervalOrdering = new Ordering() @@ -109,18 +108,21 @@ public class TaskLifecycleTest }; @Before - public void setUp() + public void setUp() throws Exception { - EmittingLogger.registerEmitter(EasyMock.createMock(ServiceEmitter.class)); + final ServiceEmitter emitter = EasyMock.createMock(ServiceEmitter.class); + EmittingLogger.registerEmitter(emitter); tmp = Files.createTempDir(); + final TaskQueueConfig tqc = new DefaultObjectMapper().readValue("{\"startDelay\":\"PT0S\"}", TaskQueueConfig.class); ts = new HeapMemoryTaskStorage(); + tsqa = new TaskStorageQueryAdapter(ts); tl = new TaskLockbox(ts); - tq = new TaskQueue(ts, tl); + tac = new LocalTaskActionClientFactory(ts, new TaskActionToolbox(tl, mdc, newMockEmitter())); + tr = new ThreadPoolTaskRunner(tb); + tq = new TaskQueue(tqc, ts, tr, tac, tl, emitter); mdc = newMockMDC(); - tac = new LocalTaskActionClientFactory(ts, new TaskActionToolbox(tq, tl, mdc, newMockEmitter())); - tb = new TaskToolboxFactory( new TaskConfig(tmp.toString(), null, null, 50000), tac, @@ -171,14 +173,7 @@ public class TaskLifecycleTest ), new DefaultObjectMapper() ); - - tr = new ThreadPoolTaskRunner(tb); - - tc = new TaskConsumer(tq, tr, tac, newMockEmitter()); - tsqa = new TaskStorageQueryAdapter(ts); - tq.start(); - tc.start(); } @After @@ -190,7 +185,6 @@ public class TaskLifecycleTest catch (Exception e) { // suppress } - tc.stop(); tq.stop(); } @@ -216,13 +210,13 @@ public class TaskLifecycleTest -1 ); - final Optional preRunTaskStatus = tsqa.getSameGroupMergedStatus(indexTask.getId()); + final Optional preRunTaskStatus = tsqa.getStatus(indexTask.getId()); Assert.assertTrue("pre run task status not present", !preRunTaskStatus.isPresent()); final TaskStatus mergedStatus = runTask(indexTask); final TaskStatus status = ts.getStatus(indexTask.getId()).get(); final List publishedSegments = byIntervalOrdering.sortedCopy(mdc.getPublished()); - final List loggedSegments = byIntervalOrdering.sortedCopy(tsqa.getSameGroupNewSegments(indexTask.getId())); + final List loggedSegments = byIntervalOrdering.sortedCopy(tsqa.getInsertedSegments(indexTask.getId())); Assert.assertEquals("statusCode", TaskStatus.Status.SUCCESS, status.getStatusCode()); Assert.assertEquals("merged statusCode", TaskStatus.Status.SUCCESS, mergedStatus.getStatusCode()); @@ -300,7 +294,13 @@ public class TaskLifecycleTest @Test public void testSimple() throws Exception { - final Task task = new AbstractTask("id1", "id1", new TaskResource("id1", 1), "ds", new Interval("2012-01-01/P1D")) + final Task task = new AbstractFixedIntervalTask( + "id1", + "id1", + new TaskResource("id1", 1), + "ds", + new Interval("2012-01-01/P1D") + ) { @Override public String getType() @@ -337,7 +337,7 @@ public class TaskLifecycleTest @Test public void testBadInterval() throws Exception { - final Task task = new AbstractTask("id1", "id1", "ds", new Interval("2012-01-01/P1D")) + final Task task = new AbstractFixedIntervalTask("id1", "id1", "ds", new Interval("2012-01-01/P1D")) { @Override public String getType() @@ -371,7 +371,7 @@ public class TaskLifecycleTest @Test public void testBadVersion() throws Exception { - final Task task = new AbstractTask("id1", "id1", "ds", new Interval("2012-01-01/P1D")) + final Task task = new AbstractFixedIntervalTask("id1", "id1", "ds", new Interval("2012-01-01/P1D")) { @Override public String getType() @@ -411,7 +411,7 @@ public class TaskLifecycleTest TaskStatus status; try { - while ((status = tsqa.getSameGroupMergedStatus(task.getId()).get()).isRunnable()) { + while ((status = tsqa.getStatus(task.getId()).get()).isRunnable()) { if (System.currentTimeMillis() > startTime + 10 * 1000) { throw new ISE("Where did the task go?!: %s", task.getId()); } diff --git a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskQueueTest.java b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskQueueTest.java deleted file mode 100644 index 2b1d0e560c1..00000000000 --- a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskQueueTest.java +++ /dev/null @@ -1,438 +0,0 @@ -/* - * Druid - a distributed column store. - * Copyright (C) 2012, 2013 Metamarkets Group Inc. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package io.druid.indexing.overlord; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import io.druid.indexing.common.SegmentLoaderFactory; -import io.druid.indexing.common.TaskLock; -import io.druid.indexing.common.TaskStatus; -import io.druid.indexing.common.TaskToolbox; -import io.druid.indexing.common.TaskToolboxFactory; -import io.druid.indexing.common.actions.LocalTaskActionClientFactory; -import io.druid.indexing.common.actions.SpawnTasksAction; -import io.druid.indexing.common.actions.TaskActionToolbox; -import io.druid.indexing.common.config.TaskConfig; -import io.druid.indexing.common.task.AbstractTask; -import io.druid.indexing.common.task.Task; -import io.druid.segment.loading.DataSegmentPuller; -import io.druid.segment.loading.LocalDataSegmentPuller; -import io.druid.segment.loading.OmniSegmentLoader; -import io.druid.segment.loading.SegmentLoaderConfig; -import io.druid.segment.loading.StorageLocationConfig; -import org.joda.time.Interval; -import org.junit.Assert; -import org.junit.Test; - -import java.util.List; -import java.util.Set; - -public class TaskQueueTest -{ - @Test - public void testEmptyQueue() throws Exception - { - final TaskStorage ts = new HeapMemoryTaskStorage(); - final TaskLockbox tl = new TaskLockbox(ts); - final TaskQueue tq = newTaskQueue(ts, tl); - - // get task status for nonexistent task - Assert.assertFalse("getStatus", ts.getStatus("foo").isPresent()); - - // poll on empty queue - Assert.assertNull("poll", tq.poll()); - } - - public static TaskQueue newTaskQueue(TaskStorage storage, TaskLockbox lockbox) - { - final TaskQueue tq = new TaskQueue(storage, lockbox); - tq.bootstrap(); - tq.start(); - return tq; - } - - @Test - public void testAddRemove() throws Exception - { - final TaskStorage ts = new HeapMemoryTaskStorage(); - final TaskLockbox tl = new TaskLockbox(ts); - final TaskQueue tq = newTaskQueue(ts, tl); - - final Task[] tasks = { - newTask("T0", "G0", "bar", new Interval("2011/P1Y")), - newTask("T1", "G1", "bar", new Interval("2011-03-01/P1D")), - newTask("T2", "G2", "foo", new Interval("2011-03-01/P1D")), - newTask("T3", "G3", "foo", new Interval("2011/P1Y")), - newTask("T4", "G4", "foo", new Interval("2012-01-02/P1D")), - newTask("T5", "G5", "foo", new Interval("2012-02-01/PT1H")) - }; - - Throwable thrown; - - for (Task task : tasks) { - tq.add(task); - } - - // get task status for in-progress task - Assert.assertEquals( - "T2 status (before finishing)", - TaskStatus.Status.RUNNING, - ts.getStatus(tasks[2].getId()).get().getStatusCode() - ); - - // Can't add tasks with the same id - thrown = null; - try { - tq.add(newTask("T5", "G5", "baz", new Interval("2013-02-01/PT1H"))); - } - catch (TaskExistsException e) { - thrown = e; - } - - Assert.assertNotNull("Exception on duplicate task id", thrown); - - // take max number of tasks - final List taken = Lists.newArrayList(); - while (true) { - final Task task = tq.poll(); - if (task != null) { - taken.add(task); - } else { - break; - } - } - - // check them - Assert.assertEquals( - "Taken tasks (round 1)", - Lists.newArrayList( - tasks[0], tasks[2], tasks[4], tasks[5] - ), - taken - ); - - // mark one done - tq.notify(tasks[2], tasks[2].run(null)); - - // get its status back - Assert.assertEquals( - "T2 status (after finishing)", - TaskStatus.Status.SUCCESS, - ts.getStatus(tasks[2].getId()).get().getStatusCode() - ); - - // We should be able to get one more task now - taken.clear(); - while (true) { - final Task task = tq.poll(); - if (task != null) { - taken.add(task); - } else { - break; - } - } - - // check it - Assert.assertEquals( - "Taken tasks (round 2)", - Lists.newArrayList( - tasks[3] - ), - taken - ); - - // there should be no more tasks to get - Assert.assertNull("poll queue with no tasks available", tq.poll()); - } - - @Test - public void testContinues() throws Exception - { - final TaskStorage ts = new HeapMemoryTaskStorage(); - final TaskLockbox tl = new TaskLockbox(ts); - final TaskQueue tq = newTaskQueue(ts, tl); - final TaskToolboxFactory tb = new TaskToolboxFactory( - new TaskConfig(null, null, null, null), - new LocalTaskActionClientFactory(ts, new TaskActionToolbox(tq, tl, null, null)), - null, - null, - null, - null, - null, - null, - null, - null, - new SegmentLoaderFactory( - new OmniSegmentLoader( - ImmutableMap.of( - "local", - new LocalDataSegmentPuller() - ), - null, - new SegmentLoaderConfig() - { - @Override - public List getLocations() - { - return Lists.newArrayList(); - } - } - ) - ), - null - ); - - final Task t0 = newTask("T0", "G0", "bar", new Interval("2011/P1Y")); - final Task t1 = newContinuedTask("T1", "G1", "bar", new Interval("2013/P1Y"), Lists.newArrayList(t0)); - tq.add(t1); - - Assert.assertTrue("T0 isPresent (#1)", !ts.getStatus("T0").isPresent()); - Assert.assertTrue("T1 isPresent (#1)", ts.getStatus("T1").isPresent()); - Assert.assertTrue("T1 isRunnable (#1)", ts.getStatus("T1").get().isRunnable()); - Assert.assertTrue("T1 isComplete (#1)", !ts.getStatus("T1").get().isComplete()); - - // should be able to get t1 out - Assert.assertEquals("poll #1", "T1", tq.poll().getId()); - Assert.assertNull("poll #2", tq.poll()); - - // report T1 done. Should cause T0 to be created - tq.notify(t1, t1.run(tb.build(t1))); - - Assert.assertTrue("T0 isPresent (#2)", ts.getStatus("T0").isPresent()); - Assert.assertTrue("T0 isRunnable (#2)", ts.getStatus("T0").get().isRunnable()); - Assert.assertTrue("T0 isComplete (#2)", !ts.getStatus("T0").get().isComplete()); - Assert.assertTrue("T1 isPresent (#2)", ts.getStatus("T1").isPresent()); - Assert.assertTrue("T1 isRunnable (#2)", !ts.getStatus("T1").get().isRunnable()); - Assert.assertTrue("T1 isComplete (#2)", ts.getStatus("T1").get().isComplete()); - - // should be able to get t0 out - Assert.assertEquals("poll #3", "T0", tq.poll().getId()); - Assert.assertNull("poll #4", tq.poll()); - - // report T0 done. Should cause T0, T1 to be marked complete - tq.notify(t0, t0.run(tb.build(t0))); - - Assert.assertTrue("T0 isPresent (#3)", ts.getStatus("T0").isPresent()); - Assert.assertTrue("T0 isRunnable (#3)", !ts.getStatus("T0").get().isRunnable()); - Assert.assertTrue("T0 isComplete (#3)", ts.getStatus("T0").get().isComplete()); - Assert.assertTrue("T1 isPresent (#3)", ts.getStatus("T1").isPresent()); - Assert.assertTrue("T1 isRunnable (#3)", !ts.getStatus("T1").get().isRunnable()); - Assert.assertTrue("T1 isComplete (#3)", ts.getStatus("T1").get().isComplete()); - - // should be no more events available for polling - Assert.assertNull("poll #5", tq.poll()); - } - - @Test - public void testConcurrency() throws Exception - { - final TaskStorage ts = new HeapMemoryTaskStorage(); - final TaskLockbox tl = new TaskLockbox(ts); - final TaskQueue tq = newTaskQueue(ts, tl); - final TaskToolboxFactory tb = new TaskToolboxFactory( - new TaskConfig(null, null, null, null), - new LocalTaskActionClientFactory(ts, new TaskActionToolbox(tq, tl, null, null)), - null, - null, - null, - null, - null, - null, - null, - null, - new SegmentLoaderFactory( - new OmniSegmentLoader( - ImmutableMap.of( - "local", - new LocalDataSegmentPuller() - ), - null, - new SegmentLoaderConfig() - { - @Override - public List getLocations() - { - return Lists.newArrayList(); - } - } - ) - ), - null - ); - - // Imagine a larger task that splits itself up into pieces - final Task t1 = newTask("T1", "G0", "bar", new Interval("2011-01-01/P1D")); - final Task t2 = newTask("T2", "G1", "bar", new Interval("2011-01-02/P1D")); // Task group different from original - final Task t3 = newTask("T3", "G0", "bar", new Interval("2011-01-03/P1D")); - final Task t4 = newTask("T4", "G0", "bar", new Interval("2011-01-02/P5D")); // Interval wider than original - final Task t0 = newContinuedTask( - "T0", - "G0", - "bar", - new Interval("2011-01-01/P3D"), - ImmutableList.of(t1, t2, t3, t4) - ); - - tq.add(t0); - - final Task wt0 = tq.poll(); - final TaskLock wt0Lock = Iterables.getOnlyElement(tl.findLocksForTask(wt0)); - Assert.assertEquals("wt0 task id", "T0", wt0.getId()); - Assert.assertNull("null poll #1", tq.poll()); - - // Sleep a bit to avoid false test passes - Thread.sleep(5); - - // Finish t0 - tq.notify(t0, t0.run(tb.build(t0))); - - // take max number of tasks - final Set taken = Sets.newHashSet(); - while (true) { - - // Sleep a bit to avoid false test passes - Thread.sleep(5); - - final Task task = tq.poll(); - - if (task != null) { - final TaskLock taskLock = Iterables.getOnlyElement(tl.findLocksForTask(task)); - Assert.assertEquals( - String.format("%s version", task.getId()), - wt0Lock.getVersion(), - taskLock.getVersion() - ); - taken.add(task.getId()); - } else { - break; - } - - } - - Assert.assertEquals("taken", Sets.newHashSet("T1", "T3"), taken); - - // Finish t1 - tq.notify(t1, t1.run(null)); - Assert.assertNull("null poll #2", tq.poll()); - - // Finish t3 - tq.notify(t3, t3.run(tb.build(t3))); - - // We should be able to get t2 now - final Task wt2 = tq.poll(); - final TaskLock wt2Lock = Iterables.getOnlyElement(tl.findLocksForTask(wt2)); - Assert.assertEquals("wt2 task id", "T2", wt2.getId()); - Assert.assertEquals("wt2 group id", "G1", wt2.getGroupId()); - Assert.assertNotSame("wt2 version", wt0Lock.getVersion(), wt2Lock.getVersion()); - Assert.assertNull("null poll #3", tq.poll()); - - // Finish t2 - tq.notify(t2, t2.run(tb.build(t2))); - - // We should be able to get t4 - // And it should be in group G0, but that group should have a different version than last time - // (Since the previous transaction named "G0" has ended and transaction names are not necessarily tied to - // one version if they end and are re-started) - final Task wt4 = tq.poll(); - final TaskLock wt4Lock = Iterables.getOnlyElement(tl.findLocksForTask(wt4)); - Assert.assertEquals("wt4 task id", "T4", wt4.getId()); - Assert.assertEquals("wt4 group id", "G0", wt4.getGroupId()); - Assert.assertNotSame("wt4 version", wt0Lock.getVersion(), wt4Lock.getVersion()); - Assert.assertNotSame("wt4 version", wt2Lock.getVersion(), wt4Lock.getVersion()); - - // Kind of done testing at this point, but let's finish t4 anyway - tq.notify(t4, t4.run(tb.build(t4))); - Assert.assertNull("null poll #4", tq.poll()); - } - - @Test - public void testBootstrap() throws Exception - { - final TaskStorage storage = new HeapMemoryTaskStorage(); - final TaskLockbox lockbox = new TaskLockbox(storage); - - storage.insert(newTask("T1", "G1", "bar", new Interval("2011-01-01/P1D")), TaskStatus.running("T1")); - storage.insert(newTask("T2", "G2", "bar", new Interval("2011-02-01/P1D")), TaskStatus.running("T2")); - storage.addLock("T1", new TaskLock("G1", "bar", new Interval("2011-01-01/P1D"), "1234")); - - final TaskQueue tq = newTaskQueue(storage, lockbox); - - final Task vt1 = tq.poll(); - final TaskLock vt1Lock = Iterables.getOnlyElement(lockbox.findLocksForTask(vt1)); - Assert.assertEquals("vt1 id", "T1", vt1.getId()); - Assert.assertEquals("vt1 version", "1234", vt1Lock.getVersion()); - - tq.notify(vt1, TaskStatus.success("T1")); - - // re-bootstrap - tq.stop(); - storage.setStatus(TaskStatus.failure("T2")); - tq.bootstrap(); - tq.start(); - - Assert.assertNull("null poll", tq.poll()); - } - - private static Task newTask(final String id, final String groupId, final String dataSource, final Interval interval) - { - return new AbstractTask(id, groupId, dataSource, interval) - { - @Override - public TaskStatus run(TaskToolbox toolbox) throws Exception - { - return TaskStatus.success(id); - } - - @Override - public String getType() - { - return "null"; - } - }; - } - - private static Task newContinuedTask( - final String id, - final String groupId, - final String dataSource, - final Interval interval, - final List nextTasks - ) - { - return new AbstractTask(id, groupId, dataSource, interval) - { - @Override - public String getType() - { - return "null"; - } - - @Override - public TaskStatus run(TaskToolbox toolbox) throws Exception - { - toolbox.getTaskActionClient().submit(new SpawnTasksAction(nextTasks)); - return TaskStatus.success(id); - } - }; - } -} diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index 3e1313c51ba..18609f7aea6 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -44,6 +44,7 @@ import io.druid.guice.PolyBind; import io.druid.indexing.common.actions.LocalTaskActionClientFactory; import io.druid.indexing.common.actions.TaskActionClientFactory; import io.druid.indexing.common.actions.TaskActionToolbox; +import io.druid.indexing.common.config.TaskConfig; import io.druid.indexing.common.index.ChatHandlerProvider; import io.druid.indexing.common.tasklogs.SwitchingTaskLogStreamer; import io.druid.indexing.common.tasklogs.TaskRunnerTaskLogStreamer; @@ -54,11 +55,10 @@ import io.druid.indexing.overlord.IndexerDBCoordinator; import io.druid.indexing.overlord.RemoteTaskRunnerFactory; import io.druid.indexing.overlord.TaskLockbox; import io.druid.indexing.overlord.TaskMaster; -import io.druid.indexing.overlord.TaskQueue; import io.druid.indexing.overlord.TaskRunnerFactory; import io.druid.indexing.overlord.TaskStorage; import io.druid.indexing.overlord.TaskStorageQueryAdapter; -import io.druid.indexing.overlord.http.OldOverlordResource; +import io.druid.indexing.overlord.config.TaskQueueConfig; import io.druid.indexing.overlord.http.OverlordRedirectInfo; import io.druid.indexing.overlord.http.OverlordResource; import io.druid.indexing.overlord.scaling.AutoScalingStrategy; @@ -115,6 +115,8 @@ public class CliOverlord extends ServerRunnable @Override public void configure(Binder binder) { + JsonConfigProvider.bind(binder, "druid.indexer.queue", TaskQueueConfig.class); + binder.bind(TaskMaster.class).in(ManageLifecycle.class); binder.bind(TaskLogStreamer.class).to(SwitchingTaskLogStreamer.class).in(LazySingleton.class); @@ -128,7 +130,6 @@ public class CliOverlord extends ServerRunnable binder.bind(TaskActionClientFactory.class).to(LocalTaskActionClientFactory.class).in(LazySingleton.class); binder.bind(TaskActionToolbox.class).in(LazySingleton.class); - binder.bind(TaskQueue.class).in(LazySingleton.class); // Lifecycle managed by TaskMaster instead binder.bind(IndexerDBCoordinator.class).in(LazySingleton.class); binder.bind(TaskLockbox.class).in(LazySingleton.class); binder.bind(TaskStorageQueryAdapter.class).in(LazySingleton.class); @@ -147,7 +148,6 @@ public class CliOverlord extends ServerRunnable binder.bind(JettyServerInitializer.class).toInstance(new OverlordJettyServerInitializer()); Jerseys.addResource(binder, OverlordResource.class); - Jerseys.addResource(binder, OldOverlordResource.class); LifecycleModule.register(binder, Server.class); } diff --git a/services/src/main/java/io/druid/cli/CliPeon.java b/services/src/main/java/io/druid/cli/CliPeon.java index db60016fc4a..2c1b4511193 100644 --- a/services/src/main/java/io/druid/cli/CliPeon.java +++ b/services/src/main/java/io/druid/cli/CliPeon.java @@ -174,7 +174,6 @@ public class CliPeon extends GuiceRunnable .to(LocalTaskActionClientFactory.class).in(LazySingleton.class); // all of these bindings are so that we can run the peon in local mode binder.bind(TaskStorage.class).to(HeapMemoryTaskStorage.class).in(LazySingleton.class); - binder.bind(TaskQueue.class).in(LazySingleton.class); binder.bind(TaskActionToolbox.class).in(LazySingleton.class); binder.bind(IndexerDBCoordinator.class).in(LazySingleton.class); taskActionBinder.addBinding("remote") From 0adda97776e2ba85d8687f5daf198728e5f004ee Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 11 Dec 2013 22:37:28 -0800 Subject: [PATCH 040/189] AbstractFixedIntervalTask: Copyright header --- .../task/AbstractFixedIntervalTask.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/AbstractFixedIntervalTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/AbstractFixedIntervalTask.java index 0b65988f216..4e2f08a992d 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/AbstractFixedIntervalTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/AbstractFixedIntervalTask.java @@ -1,3 +1,22 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + package io.druid.indexing.common.task; import com.fasterxml.jackson.annotation.JsonIgnore; From 53d90efe30d0aa103f5cbd461c0ab754b80e1083 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 11 Dec 2013 22:37:40 -0800 Subject: [PATCH 041/189] TaskQueueConfig: Copyright header --- .../overlord/config/TaskQueueConfig.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/config/TaskQueueConfig.java b/indexing-service/src/main/java/io/druid/indexing/overlord/config/TaskQueueConfig.java index 7d256b513db..ac46f2cc60f 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/config/TaskQueueConfig.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/config/TaskQueueConfig.java @@ -1,3 +1,22 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + package io.druid.indexing.overlord.config; import com.fasterxml.jackson.annotation.JsonCreator; From bed263efa532f7f0a9a4ffd094ccdc464c1854e2 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 11 Dec 2013 22:37:55 -0800 Subject: [PATCH 042/189] VersionConverterTask: Less goofy import for Preconditions --- .../io/druid/indexing/common/task/VersionConverterTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/VersionConverterTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/VersionConverterTask.java index 4e2c4870a2f..0d9dfc8f7ec 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/VersionConverterTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/VersionConverterTask.java @@ -22,8 +22,8 @@ package io.druid.indexing.common.task; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.api.client.repackaged.com.google.common.base.Preconditions; import com.google.common.base.Function; +import com.google.common.base.Preconditions; import com.google.common.collect.Sets; import com.metamx.common.guava.FunctionalIterable; import com.metamx.common.logger.Logger; From 05e24bd85ca01be352e981ad6a04495efbd2ad0a Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 11 Dec 2013 22:38:04 -0800 Subject: [PATCH 043/189] RemoteTaskRunner: Fix typo --- .../main/java/io/druid/indexing/overlord/RemoteTaskRunner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java b/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java index 4270c52b0ae..d42f32216a8 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java @@ -304,7 +304,7 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer public void shutdown(final String taskId) { if (!started) { - log.info("This TaskRunner is stopped. Ignorning shutdown command for task: %s", taskId); + log.info("This TaskRunner is stopped. Ignoring shutdown command for task: %s", taskId); } else if (pendingTasks.remove(taskId) != null) { log.info("Removed task from pending queue: %s", taskId); } else if (completeTasks.containsKey(taskId)) { From b17dc6f744ae8040f48a8e671039cd4a30d7a92d Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 11 Dec 2013 22:41:27 -0800 Subject: [PATCH 044/189] Task interval, isReady hygiene --- .../common/task/AbstractFixedIntervalTask.java | 2 +- .../io/druid/indexing/common/task/DeleteTask.java | 6 ++---- .../io/druid/indexing/common/task/IndexTask.java | 2 +- .../java/io/druid/indexing/common/task/KillTask.java | 12 ++---------- .../io/druid/indexing/common/task/MergeTaskBase.java | 6 ++---- .../indexing/common/task/VersionConverterTask.java | 7 ------- 6 files changed, 8 insertions(+), 27 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/AbstractFixedIntervalTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/AbstractFixedIntervalTask.java index 4e2f08a992d..d1d494f5c83 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/AbstractFixedIntervalTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/AbstractFixedIntervalTask.java @@ -28,7 +28,7 @@ import org.joda.time.Interval; public abstract class AbstractFixedIntervalTask extends AbstractTask { @JsonIgnore - final Interval interval; + private final Interval interval; protected AbstractFixedIntervalTask( String id, diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/DeleteTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/DeleteTask.java index 8f5a0da8f46..872ac3507bd 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/DeleteTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/DeleteTask.java @@ -30,9 +30,7 @@ import io.druid.granularity.QueryGranularity; import io.druid.indexing.common.TaskLock; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.TaskToolbox; -import io.druid.indexing.common.actions.LockTryAcquireAction; import io.druid.indexing.common.actions.SegmentInsertAction; -import io.druid.indexing.common.actions.TaskActionClient; import io.druid.query.aggregation.AggregatorFactory; import io.druid.segment.IndexMerger; import io.druid.segment.IndexableAdapter; @@ -81,13 +79,13 @@ public class DeleteTask extends AbstractFixedIntervalTask // Strategy: Create an empty segment covering the interval to be deleted final TaskLock myLock = Iterables.getOnlyElement(getTaskLocks(toolbox)); final IncrementalIndex empty = new IncrementalIndex(0, QueryGranularity.NONE, new AggregatorFactory[0]); - final IndexableAdapter emptyAdapter = new IncrementalIndexAdapter(interval, empty); + final IndexableAdapter emptyAdapter = new IncrementalIndexAdapter(getInterval(), empty); // Create DataSegment final DataSegment segment = DataSegment.builder() .dataSource(this.getDataSource()) - .interval(interval) + .interval(getInterval()) .version(myLock.getVersion()) .shardSpec(new NoneShardSpec()) .build(); diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java index a92f893ccd6..468ff68bb0a 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java @@ -160,7 +160,7 @@ public class IndexTask extends AbstractFixedIntervalTask indexGranularity, shardSpec ), - interval, + getInterval(), myLock.getVersion() ); segments.add(segment); diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/KillTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/KillTask.java index ef266b65f21..c77ddb21d96 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/KillTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/KillTask.java @@ -28,10 +28,8 @@ import com.metamx.common.logger.Logger; import io.druid.indexing.common.TaskLock; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.TaskToolbox; -import io.druid.indexing.common.actions.LockTryAcquireAction; import io.druid.indexing.common.actions.SegmentListUnusedAction; import io.druid.indexing.common.actions.SegmentNukeAction; -import io.druid.indexing.common.actions.TaskActionClient; import io.druid.timeline.DataSegment; import org.joda.time.Interval; @@ -63,12 +61,6 @@ public class KillTask extends AbstractFixedIntervalTask return "kill"; } - @Override - public boolean isReady(TaskActionClient taskActionClient) throws Exception - { - return taskActionClient.submit(new LockTryAcquireAction(interval)).isPresent(); - } - @Override public TaskStatus run(TaskToolbox toolbox) throws Exception { @@ -79,8 +71,8 @@ public class KillTask extends AbstractFixedIntervalTask throw new ISE("WTF?! Lock dataSource[%s] != task dataSource[%s]", myLock.getDataSource(), getDataSource()); } - if (!myLock.getInterval().equals(interval)) { - throw new ISE("WTF?! Lock interval[%s] != task interval[%s]", myLock.getInterval(), interval); + if (!myLock.getInterval().equals(getInterval())) { + throw new ISE("WTF?! Lock interval[%s] != task interval[%s]", myLock.getInterval(), getInterval()); } // List unused segments diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/MergeTaskBase.java b/indexing-service/src/main/java/io/druid/indexing/common/task/MergeTaskBase.java index 4f43afccc65..d49d74b355b 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/MergeTaskBase.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/MergeTaskBase.java @@ -40,7 +40,6 @@ import com.metamx.emitter.service.ServiceMetricEvent; import io.druid.indexing.common.TaskLock; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.TaskToolbox; -import io.druid.indexing.common.actions.LockTryAcquireAction; import io.druid.indexing.common.actions.SegmentInsertAction; import io.druid.indexing.common.actions.SegmentListUsedAction; import io.druid.indexing.common.actions.TaskActionClient; @@ -52,7 +51,6 @@ import org.joda.time.Interval; import javax.annotation.Nullable; import java.io.File; -import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Set; @@ -202,7 +200,7 @@ public abstract class MergeTaskBase extends AbstractFixedIntervalTask final Set current = ImmutableSet.copyOf( Iterables.transform( - taskActionClient.submit(new SegmentListUsedAction(getDataSource(), interval)), + taskActionClient.submit(new SegmentListUsedAction(getDataSource(), getInterval())), toIdentifier ) ); @@ -243,7 +241,7 @@ public abstract class MergeTaskBase extends AbstractFixedIntervalTask return Objects.toStringHelper(this) .add("id", getId()) .add("dataSource", getDataSource()) - .add("interval", interval) + .add("interval", getInterval()) .add("segments", segments) .toString(); } diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/VersionConverterTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/VersionConverterTask.java index 0d9dfc8f7ec..52807861a12 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/VersionConverterTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/VersionConverterTask.java @@ -29,7 +29,6 @@ import com.metamx.common.guava.FunctionalIterable; import com.metamx.common.logger.Logger; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.TaskToolbox; -import io.druid.indexing.common.actions.LockTryAcquireAction; import io.druid.indexing.common.actions.SegmentInsertAction; import io.druid.indexing.common.actions.SegmentListUsedAction; import io.druid.indexing.common.actions.TaskActionClient; @@ -221,12 +220,6 @@ public class VersionConverterTask extends AbstractFixedIntervalTask return "version_converter_sub"; } - @Override - public boolean isReady(TaskActionClient taskActionClient) throws Exception - { - return taskActionClient.submit(new LockTryAcquireAction(segment.getInterval())).isPresent(); - } - @Override public TaskStatus run(TaskToolbox toolbox) throws Exception { From cd8d2fd5c34c058c3723918fee1c9c53428b8e7e Mon Sep 17 00:00:00 2001 From: Hagen Rother Date: Thu, 12 Dec 2013 15:24:16 +0100 Subject: [PATCH 045/189] kafka-eight: bump kafka dependency to 0.8 release --- kafka-eight/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index a7c589553ee..c4ac5339f5e 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -41,7 +41,7 @@ org.apache.kafka kafka_2.9.2 - 0.8.0-beta1 + 0.8.0 log4j From f4a09d4ee3b53c983c71b076f2fac50022f21d93 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 12 Dec 2013 08:45:23 -0800 Subject: [PATCH 046/189] TaskAction: Add JsonSubType for LockTryAcquireAction --- .../main/java/io/druid/indexing/common/actions/TaskAction.java | 1 + 1 file changed, 1 insertion(+) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskAction.java b/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskAction.java index 937315c0ad2..d9bdfe5b694 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskAction.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskAction.java @@ -29,6 +29,7 @@ import java.io.IOException; @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @JsonSubTypes(value = { @JsonSubTypes.Type(name = "lockAcquire", value = LockAcquireAction.class), + @JsonSubTypes.Type(name = "lockTryAcquire", value = LockTryAcquireAction.class), @JsonSubTypes.Type(name = "lockList", value = LockListAction.class), @JsonSubTypes.Type(name = "lockRelease", value = LockReleaseAction.class), @JsonSubTypes.Type(name = "segmentInsertion", value = SegmentInsertAction.class), From db9b515e71846ea0a3755201c180a9145172d8ee Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 12 Dec 2013 08:46:00 -0800 Subject: [PATCH 047/189] IndexTask: Remove unnecessary args to determinePartitions. --- .../io/druid/indexing/common/task/IndexTask.java | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java index 468ff68bb0a..561933b3902 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java @@ -135,18 +135,7 @@ public class IndexTask extends AbstractFixedIntervalTask for (final Interval bucket : granularitySpec.bucketIntervals()) { final List shardSpecs; if (targetPartitionSize > 0) { - shardSpecs = determinePartitions( - toolbox, - new Schema( - getDataSource(), - spatialDimensions, - aggregators, - indexGranularity, - new NoneShardSpec() // Dummy shardSpec, won't be used - ), - bucket, - targetPartitionSize - ); + shardSpecs = determinePartitions(bucket, targetPartitionSize); } else { shardSpecs = ImmutableList.of(new NoneShardSpec()); } @@ -170,8 +159,6 @@ public class IndexTask extends AbstractFixedIntervalTask } private List determinePartitions( - final TaskToolbox toolbox, - final Schema schema, final Interval interval, final int targetPartitionSize ) throws IOException From b6a52610bc24d411a99d5a95dee2e654d5fd25b4 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 12 Dec 2013 08:46:10 -0800 Subject: [PATCH 048/189] IndexTask: Call plumber.startJob() --- .../src/main/java/io/druid/indexing/common/task/IndexTask.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java index 561933b3902..24aa69c7bf0 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java @@ -334,6 +334,8 @@ public class IndexTask extends AbstractFixedIntervalTask : toolbox.getConfig().getDefaultRowFlushBoundary(); try { + plumber.startJob(); + while (firehose.hasMore()) { final InputRow inputRow = firehose.nextRow(); From d92b88718ce1a765177be055932372829d1a92fc Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 12 Dec 2013 08:46:24 -0800 Subject: [PATCH 049/189] OverlordResource: Fix comment --- .../java/io/druid/indexing/overlord/http/OverlordResource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java b/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java index 4e947e4b55d..7ca0bfed2c9 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java @@ -220,7 +220,7 @@ public class OverlordResource final Map retMap; // It would be great to verify that this worker is actually supposed to be running the task before - // actually doing the task. Some ideas for how that could be done would be using some sort of attempt_id + // actually doing the action. Some ideas for how that could be done would be using some sort of attempt_id // or token that gets passed around. try { From 0129ea99cf288fac2d1d9c6c14660c24fb7cd6a2 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 12 Dec 2013 10:44:46 -0800 Subject: [PATCH 050/189] RemoteTaskRunner changes to make bootstrapping actually work. - Workers are not added to zkWorkers until caches have been initialized. - Worker status we haven't heard about will be added to runningTasks or completeTasks as appropriate. - TaskRunnerWorkItem now only needs a taskId, not the entire Task. This makes it possible to create them from TaskStatus objects, if that's all we have. - Also remove some dead code. --- .../indexing/overlord/ForkingTaskRunner.java | 6 +- .../indexing/overlord/RemoteTaskRunner.java | 157 ++++++++++-------- .../overlord/RemoteTaskRunnerWorkItem.java | 13 +- .../druid/indexing/overlord/TaskLockbox.java | 14 -- .../druid/indexing/overlord/TaskMaster.java | 16 +- .../io/druid/indexing/overlord/TaskQueue.java | 2 +- .../indexing/overlord/TaskRunnerWorkItem.java | 26 ++- .../overlord/ThreadPoolTaskRunner.java | 51 +++--- .../io/druid/indexing/overlord/ZkWorker.java | 4 +- .../overlord/http/OverlordResource.java | 4 +- .../overlord/RemoteTaskRunnerTest.java | 8 +- .../SimpleResourceManagementStrategyTest.java | 16 +- 12 files changed, 160 insertions(+), 157 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/ForkingTaskRunner.java b/indexing-service/src/main/java/io/druid/indexing/overlord/ForkingTaskRunner.java index d95b9e57429..fce401c6641 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/ForkingTaskRunner.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/ForkingTaskRunner.java @@ -109,7 +109,7 @@ public class ForkingTaskRunner implements TaskRunner, TaskLogStreamer tasks.put( task.getId(), new ForkingTaskRunnerWorkItem( - task, + task.getId(), exec.submit( new Callable() { @@ -427,11 +427,11 @@ public class ForkingTaskRunner implements TaskRunner, TaskLogStreamer private volatile ProcessHolder processHolder = null; private ForkingTaskRunnerWorkItem( - Task task, + String taskId, ListenableFuture statusFuture ) { - super(task, statusFuture); + super(taskId, statusFuture); } } diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java b/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java index d42f32216a8..b6441049b2f 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java @@ -52,7 +52,6 @@ import io.druid.indexing.worker.Worker; import io.druid.server.initialization.ZkPathsConfig; import io.druid.tasklogs.TaskLogStreamer; import org.apache.curator.framework.CuratorFramework; -import org.apache.curator.framework.recipes.cache.ChildData; import org.apache.curator.framework.recipes.cache.PathChildrenCache; import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener; @@ -72,6 +71,8 @@ import java.util.Map; import java.util.TreeSet; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -108,11 +109,13 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer private final HttpClient httpClient; // all workers that exist in ZK - private final Map zkWorkers = new ConcurrentHashMap(); + private final ConcurrentMap zkWorkers = new ConcurrentHashMap<>(); + // payloads of pending tasks, which we remember just long enough to assign to workers + private final ConcurrentMap pendingTaskPayloads = new ConcurrentHashMap<>(); + // tasks that have not yet been assigned to a worker + private final RemoteTaskRunnerWorkQueue pendingTasks = new RemoteTaskRunnerWorkQueue(); // all tasks that have been assigned to a worker private final RemoteTaskRunnerWorkQueue runningTasks = new RemoteTaskRunnerWorkQueue(); - // tasks that have not yet run - private final RemoteTaskRunnerWorkQueue pendingTasks = new RemoteTaskRunnerWorkQueue(); // tasks that are complete but not cleaned up yet private final RemoteTaskRunnerWorkQueue completeTasks = new RemoteTaskRunnerWorkQueue(); @@ -150,6 +153,8 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer return; } + final CountDownLatch initialized = new CountDownLatch(1); + // Add listener for creation/deletion of workers workerPathCache.getListenable().addListener( new PathChildrenCacheListener() @@ -164,7 +169,7 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer event.getData().getData(), Worker.class ); - addWorker(worker, PathChildrenCache.StartMode.NORMAL); + addWorker(worker); break; case CHILD_REMOVED: worker = jsonMapper.readValue( @@ -173,22 +178,17 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer ); removeWorker(worker); break; + case INITIALIZED: + initialized.countDown(); default: break; } } } ); - workerPathCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE); - - for (ChildData childData : workerPathCache.getCurrentData()) { - final Worker worker = jsonMapper.readValue( - childData.getData(), - Worker.class - ); - addWorker(worker, PathChildrenCache.StartMode.BUILD_INITIAL_CACHE); - } + workerPathCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT); + initialized.await(); started = true; } catch (Exception e) { @@ -249,11 +249,10 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer return null; } - public boolean isWorkerRunningTask(Worker worker, Task task) + public boolean isWorkerRunningTask(Worker worker, String taskId) { ZkWorker zkWorker = zkWorkers.get(worker.getHost()); - - return (zkWorker != null && zkWorker.isRunningTask(task.getId())); + return (zkWorker != null && zkWorker.isRunningTask(taskId)); } /** @@ -264,7 +263,7 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer @Override public ListenableFuture run(final Task task) { - RemoteTaskRunnerWorkItem runningTask = runningTasks.get(task.getId()); + final RemoteTaskRunnerWorkItem runningTask = runningTasks.get(task.getId()); if (runningTask != null) { ZkWorker zkWorker = findWorkerRunningTask(task.getId()); if (zkWorker == null) { @@ -285,14 +284,7 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer log.info("Assigned a task[%s] that is already pending, not doing anything", task.getId()); return pendingTask.getResult(); } - - RemoteTaskRunnerWorkItem taskRunnerWorkItem = new RemoteTaskRunnerWorkItem( - task, - SettableFuture.create(), - null - ); - addPendingTask(taskRunnerWorkItem); - return taskRunnerWorkItem.getResult(); + return addPendingTask(task).getResult(); } /** @@ -391,12 +383,18 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer /** * Adds a task to the pending queue */ - private void addPendingTask(final RemoteTaskRunnerWorkItem taskRunnerWorkItem) + private RemoteTaskRunnerWorkItem addPendingTask(final Task task) { - log.info("Added pending task %s", taskRunnerWorkItem.getTask().getId()); - - pendingTasks.put(taskRunnerWorkItem.getTask().getId(), taskRunnerWorkItem); + log.info("Added pending task %s", task.getId()); + final RemoteTaskRunnerWorkItem taskRunnerWorkItem = new RemoteTaskRunnerWorkItem( + task.getId(), + SettableFuture.create(), + null + ); + pendingTaskPayloads.put(task.getId(), task); + pendingTasks.put(task.getId(), taskRunnerWorkItem); runPendingTasks(); + return taskRunnerWorkItem; } /** @@ -413,11 +411,14 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer public Void call() throws Exception { try { - // make a copy of the pending tasks because assignTask may delete tasks from pending and move them + // make a copy of the pending tasks because tryAssignTask may delete tasks from pending and move them // into running status List copy = Lists.newArrayList(pendingTasks.values()); - for (RemoteTaskRunnerWorkItem taskWrapper : copy) { - assignTask(taskWrapper); + for (RemoteTaskRunnerWorkItem taskRunnerWorkItem : copy) { + String taskId = taskRunnerWorkItem.getTaskId(); + if (tryAssignTask(pendingTaskPayloads.get(taskId), taskRunnerWorkItem)) { + pendingTaskPayloads.remove(taskId); + } } } catch (Exception e) { @@ -464,26 +465,34 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer * needs to bootstrap after a restart. * * @param taskRunnerWorkItem - the task to assign + * @return true iff the task is now assigned */ - private void assignTask(RemoteTaskRunnerWorkItem taskRunnerWorkItem) + private boolean tryAssignTask(final Task task, final RemoteTaskRunnerWorkItem taskRunnerWorkItem) { try { - final String taskId = taskRunnerWorkItem.getTask().getId(); + Preconditions.checkNotNull(task, "task"); + Preconditions.checkNotNull(taskRunnerWorkItem, "taskRunnerWorkItem"); + Preconditions.checkArgument(task.getId().equals(taskRunnerWorkItem.getTaskId()), "task id != workItem id"); - if (runningTasks.containsKey(taskId) || findWorkerRunningTask(taskId) != null) { - log.info("Task[%s] already running.", taskId); + if (runningTasks.containsKey(task.getId()) || findWorkerRunningTask(task.getId()) != null) { + log.info("Task[%s] already running.", task.getId()); + return true; } else { // Nothing running this task, announce it in ZK for a worker to run it - ZkWorker zkWorker = findWorkerForTask(taskRunnerWorkItem.getTask()); + ZkWorker zkWorker = findWorkerForTask(task); if (zkWorker != null) { - announceTask(zkWorker, taskRunnerWorkItem); + announceTask(task, zkWorker, taskRunnerWorkItem); + return true; + } else { + return false; } } } catch (Exception e) { log.makeAlert(e, "Exception while trying to run task") - .addData("taskId", taskRunnerWorkItem.getTask().getId()) + .addData("taskId", taskRunnerWorkItem.getTaskId()) .emit(); + return false; } } @@ -494,9 +503,13 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer * @param theZkWorker The worker the task is assigned to * @param taskRunnerWorkItem The task to be assigned */ - private void announceTask(ZkWorker theZkWorker, RemoteTaskRunnerWorkItem taskRunnerWorkItem) throws Exception + private void announceTask( + final Task task, + final ZkWorker theZkWorker, + final RemoteTaskRunnerWorkItem taskRunnerWorkItem + ) throws Exception { - final Task task = taskRunnerWorkItem.getTask(); + Preconditions.checkArgument(task.getId().equals(taskRunnerWorkItem.getTaskId()), "task id != workItem id"); final Worker theWorker = theZkWorker.getWorker(); log.info("Coordinator asking Worker[%s] to add task[%s]", theWorker.getHost(), task.getId()); @@ -533,7 +546,7 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer Stopwatch timeoutStopwatch = new Stopwatch(); timeoutStopwatch.start(); synchronized (statusLock) { - while (!isWorkerRunningTask(theWorker, task)) { + while (!isWorkerRunningTask(theWorker, task.getId())) { final long waitMs = config.getTaskAssignmentTimeout().toStandardDuration().getMillis(); statusLock.wait(waitMs); long elapsed = timeoutStopwatch.elapsed(TimeUnit.MILLISECONDS); @@ -558,9 +571,9 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer * the worker. Status changes indicate the creation or completion of a task. * The RemoteTaskRunner updates state according to these changes. * - * @param worker - contains metadata for a worker that has appeared in ZK + * @param worker contains metadata for a worker that has appeared in ZK */ - private ZkWorker addWorker(final Worker worker, PathChildrenCache.StartMode startMode) + private ZkWorker addWorker(final Worker worker) { log.info("Worker[%s] reportin' for duty!", worker.getHost()); @@ -580,8 +593,8 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer @Override public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception { - String taskId; - RemoteTaskRunnerWorkItem taskRunnerWorkItem; + final String taskId; + final RemoteTaskRunnerWorkItem taskRunnerWorkItem; synchronized (statusLock) { try { switch (event.getType()) { @@ -600,15 +613,23 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer ); // Synchronizing state with ZK - statusLock.notify(); + statusLock.notifyAll(); - taskRunnerWorkItem = runningTasks.get(taskId); - if (taskRunnerWorkItem == null) { + final RemoteTaskRunnerWorkItem tmp; + if ((tmp = runningTasks.get(taskId)) != null) { + taskRunnerWorkItem = tmp; + } else { log.warn( - "WTF?! Worker[%s] announcing a status for a task I didn't know about: %s", + "Worker[%s] announced a status for a task I didn't know about, adding to runningTasks: %s", zkWorker.getWorker().getHost(), taskId ); + taskRunnerWorkItem = new RemoteTaskRunnerWorkItem( + taskId, + SettableFuture.create(), + zkWorker.getWorker() + ); + runningTasks.put(taskId, taskRunnerWorkItem); } if (taskStatus.isComplete()) { @@ -621,11 +642,19 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer taskRunnerWorkItem = runningTasks.remove(taskId); if (taskRunnerWorkItem != null) { log.info("Task[%s] just disappeared!", taskId); - taskRunnerWorkItem.setResult(TaskStatus.failure(taskRunnerWorkItem.getTask().getId())); + taskRunnerWorkItem.setResult(TaskStatus.failure(taskRunnerWorkItem.getTaskId())); } else { log.info("Task[%s] went bye bye.", taskId); } break; + case INITIALIZED: + if (zkWorkers.putIfAbsent(worker.getHost(), zkWorker) != null) { + log.makeAlert("WTF?! Tried to add already-existing worker[%s]", worker.getHost()) + .addData("workerHost", worker.getHost()) + .addData("workerIp", worker.getIp()) + .emit(); + } + runPendingTasks(); } } catch (Exception e) { @@ -638,12 +667,7 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer } } ); - - zkWorker.start(startMode); - zkWorkers.put(worker.getHost(), zkWorker); - - runPendingTasks(); - + zkWorker.start(); return zkWorker; } catch (Exception e) { @@ -690,7 +714,7 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer } log.info("Failing task[%s]", assignedTask); - taskRunnerWorkItem.setResult(TaskStatus.failure(taskRunnerWorkItem.getTask().getId())); + taskRunnerWorkItem.setResult(TaskStatus.failure(taskRunnerWorkItem.getTaskId())); } else { log.warn("RemoteTaskRunner has no knowledge of task[%s]", assignedTask); } @@ -749,19 +773,18 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer TaskStatus taskStatus ) { + Preconditions.checkNotNull(taskRunnerWorkItem, "taskRunnerWorkItem"); + Preconditions.checkNotNull(zkWorker, "zkWorker"); + Preconditions.checkNotNull(taskStatus, "taskStatus"); // Worker is done with this task zkWorker.setLastCompletedTaskTime(new DateTime()); // Move from running -> complete - if (taskRunnerWorkItem != null) { - completeTasks.put(taskStatus.getId(), taskRunnerWorkItem); - } + completeTasks.put(taskStatus.getId(), taskRunnerWorkItem); runningTasks.remove(taskStatus.getId()); // Notify interested parties - if (taskRunnerWorkItem != null) { - final ListenableFuture result = taskRunnerWorkItem.getResult(); - if (result != null) { - ((SettableFuture) result).set(taskStatus); - } + final ListenableFuture result = taskRunnerWorkItem.getResult(); + if (result != null) { + ((SettableFuture) result).set(taskStatus); } } } diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunnerWorkItem.java b/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunnerWorkItem.java index 1c1dc7a17a9..76d373a049a 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunnerWorkItem.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunnerWorkItem.java @@ -21,7 +21,6 @@ package io.druid.indexing.overlord; import com.google.common.util.concurrent.SettableFuture; import io.druid.indexing.common.TaskStatus; -import io.druid.indexing.common.task.Task; import io.druid.indexing.worker.Worker; import org.joda.time.DateTime; @@ -33,25 +32,25 @@ public class RemoteTaskRunnerWorkItem extends TaskRunnerWorkItem private final Worker worker; public RemoteTaskRunnerWorkItem( - Task task, + String taskId, SettableFuture result, Worker worker ) { - super(task, result); + super(taskId, result); this.result = result; this.worker = worker; } public RemoteTaskRunnerWorkItem( - Task task, + String taskId, SettableFuture result, DateTime createdTime, DateTime queueInsertionTime, Worker worker ) { - super(task, result, createdTime, queueInsertionTime); + super(taskId, result, createdTime, queueInsertionTime); this.result = result; this.worker = worker; } @@ -69,11 +68,11 @@ public class RemoteTaskRunnerWorkItem extends TaskRunnerWorkItem @Override public RemoteTaskRunnerWorkItem withQueueInsertionTime(DateTime time) { - return new RemoteTaskRunnerWorkItem(getTask(), result, getCreatedTime(), time, worker); + return new RemoteTaskRunnerWorkItem(getTaskId(), result, getCreatedTime(), time, worker); } public RemoteTaskRunnerWorkItem withWorker(Worker theWorker) { - return new RemoteTaskRunnerWorkItem(getTask(), result, getCreatedTime(), getQueueInsertionTime(), theWorker); + return new RemoteTaskRunnerWorkItem(getTaskId(), result, getCreatedTime(), getQueueInsertionTime(), theWorker); } } diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskLockbox.java b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskLockbox.java index 3f6708bd451..d486f37c0fc 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskLockbox.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskLockbox.java @@ -393,20 +393,6 @@ public class TaskLockbox } } - /** - * Removes all locks from this lockbox. - */ - public void clear() - { - giant.lock(); - - try { - running.clear(); - } finally { - giant.unlock(); - } - } - /** * Return the currently-active lock posses for some task. * diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskMaster.java b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskMaster.java index b2d1eedf049..96183d5ae64 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskMaster.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskMaster.java @@ -117,6 +117,14 @@ public class TaskMaster .emit(); } leaderLifecycle.addManagedInstance(taskRunner); + if (taskRunner instanceof RemoteTaskRunner) { + final ScheduledExecutorFactory executorFactory = ScheduledExecutors.createFactory(leaderLifecycle); + resourceManagementScheduler = managementSchedulerFactory.build( + (RemoteTaskRunner) taskRunner, + executorFactory + ); + leaderLifecycle.addManagedInstance(resourceManagementScheduler); + } leaderLifecycle.addManagedInstance(taskQueue); leaderLifecycle.addHandler( new Lifecycle.Handler() @@ -134,14 +142,6 @@ public class TaskMaster } } ); - if (taskRunner instanceof RemoteTaskRunner) { - final ScheduledExecutorFactory executorFactory = ScheduledExecutors.createFactory(leaderLifecycle); - resourceManagementScheduler = managementSchedulerFactory.build( - (RemoteTaskRunner) taskRunner, - executorFactory - ); - leaderLifecycle.addManagedInstance(resourceManagementScheduler); - } try { leaderLifecycle.start(); leading = true; diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskQueue.java b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskQueue.java index f7b3c86874e..77e4b26e372 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskQueue.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskQueue.java @@ -221,7 +221,7 @@ public class TaskQueue // Task futures available from the taskRunner final Map> runnerTaskFutures = Maps.newHashMap(); for (final TaskRunnerWorkItem workItem : taskRunner.getKnownTasks()) { - runnerTaskFutures.put(workItem.getTask().getId(), workItem.getResult()); + runnerTaskFutures.put(workItem.getTaskId(), workItem.getResult()); } // Attain futures for all active tasks (assuming they are ready to run). for (final Task task : tasks) { diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskRunnerWorkItem.java b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskRunnerWorkItem.java index 4d4cac6ef70..a78faa24d03 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskRunnerWorkItem.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskRunnerWorkItem.java @@ -19,11 +19,9 @@ package io.druid.indexing.overlord; -import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ComparisonChain; import com.google.common.util.concurrent.ListenableFuture; import io.druid.indexing.common.TaskStatus; -import io.druid.indexing.common.task.Task; import org.joda.time.DateTime; import org.joda.time.DateTimeComparator; @@ -32,36 +30,35 @@ import org.joda.time.DateTimeComparator; */ public class TaskRunnerWorkItem implements Comparable { - private final Task task; + private final String taskId; private final ListenableFuture result; private final DateTime createdTime; private final DateTime queueInsertionTime; public TaskRunnerWorkItem( - Task task, + String taskId, ListenableFuture result ) { - this(task, result, new DateTime(), new DateTime()); + this(taskId, result, new DateTime(), new DateTime()); } public TaskRunnerWorkItem( - Task task, + String taskId, ListenableFuture result, DateTime createdTime, DateTime queueInsertionTime ) { - this.task = task; + this.taskId = taskId; this.result = result; this.createdTime = createdTime; this.queueInsertionTime = queueInsertionTime; } - @JsonProperty - public Task getTask() + public String getTaskId() { - return task; + return taskId; } public ListenableFuture getResult() @@ -69,13 +66,11 @@ public class TaskRunnerWorkItem implements Comparable return result; } - @JsonProperty public DateTime getCreatedTime() { return createdTime; } - @JsonProperty public DateTime getQueueInsertionTime() { return queueInsertionTime; @@ -83,7 +78,7 @@ public class TaskRunnerWorkItem implements Comparable public TaskRunnerWorkItem withQueueInsertionTime(DateTime time) { - return new TaskRunnerWorkItem(task, result, createdTime, time); + return new TaskRunnerWorkItem(taskId, result, createdTime, time); } @Override @@ -91,7 +86,7 @@ public class TaskRunnerWorkItem implements Comparable { return ComparisonChain.start() .compare(createdTime, taskRunnerWorkItem.getCreatedTime(), DateTimeComparator.getInstance()) - .compare(task.getId(), taskRunnerWorkItem.getTask().getId()) + .compare(taskId, taskRunnerWorkItem.getTaskId()) .result(); } @@ -99,9 +94,10 @@ public class TaskRunnerWorkItem implements Comparable public String toString() { return "TaskRunnerWorkItem{" + - "task=" + task + + "taskId='" + taskId + '\'' + ", result=" + result + ", createdTime=" + createdTime + + ", queueInsertionTime=" + queueInsertionTime + '}'; } } diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/ThreadPoolTaskRunner.java b/indexing-service/src/main/java/io/druid/indexing/overlord/ThreadPoolTaskRunner.java index 14e3e94711a..a4a8db0d1a3 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/ThreadPoolTaskRunner.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/ThreadPoolTaskRunner.java @@ -19,7 +19,6 @@ package io.druid.indexing.overlord; -import com.google.common.base.Function; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; @@ -46,7 +45,6 @@ import org.joda.time.Interval; import java.io.File; import java.util.Collection; -import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentSkipListSet; @@ -58,7 +56,7 @@ public class ThreadPoolTaskRunner implements TaskRunner, QuerySegmentWalker { private final TaskToolboxFactory toolboxFactory; private final ListeningExecutorService exec; - private final Set runningItems = new ConcurrentSkipListSet(); + private final Set runningItems = new ConcurrentSkipListSet<>(); private static final EmittingLogger log = new EmittingLogger(ThreadPoolTaskRunner.class); @@ -82,8 +80,7 @@ public class ThreadPoolTaskRunner implements TaskRunner, QuerySegmentWalker { final TaskToolbox toolbox = toolboxFactory.build(task); final ListenableFuture statusFuture = exec.submit(new ThreadPoolTaskRunnerCallable(task, toolbox)); - - final TaskRunnerWorkItem taskRunnerWorkItem = new TaskRunnerWorkItem(task, statusFuture); + final ThreadPoolTaskRunnerWorkItem taskRunnerWorkItem = new ThreadPoolTaskRunnerWorkItem(task, statusFuture); runningItems.add(taskRunnerWorkItem); Futures.addCallback( statusFuture, new FutureCallback() @@ -109,7 +106,7 @@ public class ThreadPoolTaskRunner implements TaskRunner, QuerySegmentWalker public void shutdown(final String taskid) { for (final TaskRunnerWorkItem runningItem : runningItems) { - if (runningItem.getTask().getId().equals(taskid)) { + if (runningItem.getTaskId().equals(taskid)) { runningItem.getResult().cancel(true); } } @@ -118,7 +115,7 @@ public class ThreadPoolTaskRunner implements TaskRunner, QuerySegmentWalker @Override public Collection getRunningTasks() { - return ImmutableList.copyOf(runningItems); + return ImmutableList.copyOf(runningItems); } @Override @@ -130,7 +127,7 @@ public class ThreadPoolTaskRunner implements TaskRunner, QuerySegmentWalker @Override public Collection getKnownTasks() { - return ImmutableList.copyOf(runningItems); + return ImmutableList.copyOf(runningItems); } @Override @@ -155,18 +152,8 @@ public class ThreadPoolTaskRunner implements TaskRunner, QuerySegmentWalker { QueryRunner queryRunner = null; - final List runningTasks = Lists.transform( - ImmutableList.copyOf(getRunningTasks()), new Function() - { - @Override - public Task apply(TaskRunnerWorkItem o) - { - return o.getTask(); - } - } - ); - - for (final Task task : runningTasks) { + for (final ThreadPoolTaskRunnerWorkItem taskRunnerWorkItem : ImmutableList.copyOf(runningItems)) { + final Task task = taskRunnerWorkItem.getTask(); if (task.getDataSource().equals(query.getDataSource())) { final QueryRunner taskQueryRunner = task.getQueryRunner(query); @@ -185,6 +172,25 @@ public class ThreadPoolTaskRunner implements TaskRunner, QuerySegmentWalker return queryRunner == null ? new NoopQueryRunner() : queryRunner; } + private static class ThreadPoolTaskRunnerWorkItem extends TaskRunnerWorkItem + { + private final Task task; + + private ThreadPoolTaskRunnerWorkItem( + Task task, + ListenableFuture result + ) + { + super(task.getId(), result); + this.task = task; + } + + public Task getTask() + { + return task; + } + } + private static class ThreadPoolTaskRunnerCallable implements Callable { private final Task task; @@ -242,10 +248,5 @@ public class ThreadPoolTaskRunner implements TaskRunner, QuerySegmentWalker throw Throwables.propagate(e); } } - - public TaskRunnerWorkItem getTaskRunnerWorkItem() - { - return new TaskRunnerWorkItem(task, null); - } } } diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/ZkWorker.java b/indexing-service/src/main/java/io/druid/indexing/overlord/ZkWorker.java index edb0c3df685..335b5fa583d 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/ZkWorker.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/ZkWorker.java @@ -71,9 +71,9 @@ public class ZkWorker implements Closeable }; } - public void start(PathChildrenCache.StartMode startMode) throws Exception + public void start() throws Exception { - statusCache.start(startMode); + statusCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT); } public void addListener(PathChildrenCacheListener listener) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java b/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java index 7ca0bfed2c9..d3d58bef03d 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java @@ -70,9 +70,7 @@ public class OverlordResource public Map apply(TaskRunnerWorkItem input) { return new ImmutableMap.Builder() - .put("id", input.getTask().getId()) - .put("dataSource", input.getTask().getDataSource()) - .put("nodeType", input.getTask().getNodeType() == null ? "" : input.getTask().getNodeType()) + .put("id", input.getTaskId()) .put("createdTime", input.getCreatedTime()) .put("queueInsertionTime", input.getQueueInsertionTime()) .build(); diff --git a/indexing-service/src/test/java/io/druid/indexing/overlord/RemoteTaskRunnerTest.java b/indexing-service/src/test/java/io/druid/indexing/overlord/RemoteTaskRunnerTest.java index 9ee11d418b4..fc0b9fdb2e2 100644 --- a/indexing-service/src/test/java/io/druid/indexing/overlord/RemoteTaskRunnerTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/overlord/RemoteTaskRunnerTest.java @@ -219,7 +219,7 @@ public class RemoteTaskRunnerTest ) ); - Assert.assertTrue(remoteTaskRunner.getPendingTasks().iterator().next().getTask().getId().equals("rt2")); + Assert.assertTrue(remoteTaskRunner.getPendingTasks().iterator().next().getTaskId().equals("rt2")); } @Test @@ -266,7 +266,7 @@ public class RemoteTaskRunnerTest ) ); - Assert.assertTrue(remoteTaskRunner.getPendingTasks().iterator().next().getTask().getId().equals("rt2")); + Assert.assertTrue(remoteTaskRunner.getPendingTasks().iterator().next().getTaskId().equals("rt2")); } @Test @@ -280,7 +280,7 @@ public class RemoteTaskRunnerTest Assert.assertTrue(workerRunningTask(task.getId())); - Assert.assertTrue(remoteTaskRunner.getRunningTasks().iterator().next().getTask().getId().equals("task")); + Assert.assertTrue(remoteTaskRunner.getRunningTasks().iterator().next().getTaskId().equals("task")); cf.delete().forPath(joiner.join(statusPath, task.getId())); @@ -320,7 +320,7 @@ public class RemoteTaskRunnerTest @Override public String apply(RemoteTaskRunnerWorkItem input) { - return input.getTask().getId(); + return input.getTaskId(); } } ) diff --git a/indexing-service/src/test/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategyTest.java b/indexing-service/src/test/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategyTest.java index 02ac9a21778..1f3f4a44eee 100644 --- a/indexing-service/src/test/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategyTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategyTest.java @@ -111,7 +111,7 @@ public class SimpleResourceManagementStrategyTest boolean provisionedSomething = simpleResourceManagementStrategy.doProvision( Arrays.asList( - new RemoteTaskRunnerWorkItem(testTask, null, null).withQueueInsertionTime(new DateTime()) + new RemoteTaskRunnerWorkItem(testTask.getId(), null, null).withQueueInsertionTime(new DateTime()) ), Arrays.asList( new TestZkWorker(testTask) @@ -139,7 +139,7 @@ public class SimpleResourceManagementStrategyTest boolean provisionedSomething = simpleResourceManagementStrategy.doProvision( Arrays.asList( - new RemoteTaskRunnerWorkItem(testTask, null, null).withQueueInsertionTime(new DateTime()) + new RemoteTaskRunnerWorkItem(testTask.getId(), null, null).withQueueInsertionTime(new DateTime()) ), Arrays.asList( new TestZkWorker(testTask) @@ -155,7 +155,7 @@ public class SimpleResourceManagementStrategyTest provisionedSomething = simpleResourceManagementStrategy.doProvision( Arrays.asList( - new RemoteTaskRunnerWorkItem(testTask, null, null).withQueueInsertionTime(new DateTime()) + new RemoteTaskRunnerWorkItem(testTask.getId(), null, null).withQueueInsertionTime(new DateTime()) ), Arrays.asList( new TestZkWorker(testTask) @@ -196,7 +196,7 @@ public class SimpleResourceManagementStrategyTest boolean provisionedSomething = simpleResourceManagementStrategy.doProvision( Arrays.asList( - new RemoteTaskRunnerWorkItem(testTask, null, null).withQueueInsertionTime(new DateTime()) + new RemoteTaskRunnerWorkItem(testTask.getId(), null, null).withQueueInsertionTime(new DateTime()) ), Arrays.asList( new TestZkWorker(testTask) @@ -214,7 +214,7 @@ public class SimpleResourceManagementStrategyTest provisionedSomething = simpleResourceManagementStrategy.doProvision( Arrays.asList( - new RemoteTaskRunnerWorkItem(testTask, null, null).withQueueInsertionTime(new DateTime()) + new RemoteTaskRunnerWorkItem(testTask.getId(), null, null).withQueueInsertionTime(new DateTime()) ), Arrays.asList( new TestZkWorker(testTask) @@ -248,7 +248,7 @@ public class SimpleResourceManagementStrategyTest boolean terminatedSomething = simpleResourceManagementStrategy.doTerminate( Arrays.asList( - new RemoteTaskRunnerWorkItem(testTask, null, null).withQueueInsertionTime(new DateTime()) + new RemoteTaskRunnerWorkItem(testTask.getId(), null, null).withQueueInsertionTime(new DateTime()) ), Arrays.asList( new TestZkWorker(null) @@ -278,7 +278,7 @@ public class SimpleResourceManagementStrategyTest boolean terminatedSomething = simpleResourceManagementStrategy.doTerminate( Arrays.asList( - new RemoteTaskRunnerWorkItem(testTask, null, null).withQueueInsertionTime(new DateTime()) + new RemoteTaskRunnerWorkItem(testTask.getId(), null, null).withQueueInsertionTime(new DateTime()) ), Arrays.asList( new TestZkWorker(null) @@ -293,7 +293,7 @@ public class SimpleResourceManagementStrategyTest terminatedSomething = simpleResourceManagementStrategy.doTerminate( Arrays.asList( - new RemoteTaskRunnerWorkItem(testTask, null, null).withQueueInsertionTime(new DateTime()) + new RemoteTaskRunnerWorkItem(testTask.getId(), null, null).withQueueInsertionTime(new DateTime()) ), Arrays.asList( new TestZkWorker(null) From c60158a21a58dee6106628ddd44f7b63ec369412 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 12 Dec 2013 10:59:08 -0800 Subject: [PATCH 051/189] RemoteTaskRunner: Remove task from pendingTaskPayloads on shutdown if needed --- .../main/java/io/druid/indexing/overlord/RemoteTaskRunner.java | 1 + 1 file changed, 1 insertion(+) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java b/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java index b6441049b2f..87bccea7cc9 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java @@ -298,6 +298,7 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer if (!started) { log.info("This TaskRunner is stopped. Ignoring shutdown command for task: %s", taskId); } else if (pendingTasks.remove(taskId) != null) { + pendingTaskPayloads.remove(taskId); log.info("Removed task from pending queue: %s", taskId); } else if (completeTasks.containsKey(taskId)) { cleanup(completeTasks.get(taskId).getWorker().getHost(), taskId); From 58d8f0046a9c04f22c010e81ba440f70519d458e Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 12 Dec 2013 11:46:30 -0800 Subject: [PATCH 052/189] Configuration.md: Update TaskLogs docs --- docs/content/Configuration.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/content/Configuration.md b/docs/content/Configuration.md index 51afb07ae65..697e362b827 100644 --- a/docs/content/Configuration.md +++ b/docs/content/Configuration.md @@ -308,21 +308,29 @@ This module is used to configure the [Indexing Service](Indexing-Service.html) t |Property|Description|Default| |--------|-----------|-------| -|`druid.indexer.logs.type`|Choices:noop, S3. Where to store task logs|noop| +|`druid.indexer.logs.type`|Choices:noop, s3, file. Where to store task logs|file| -#### Noop Task Logs +#### File Task Logs -No task logs are actually stored. +Store task logs in the local filesystem. + +|Property|Description|Default| +|--------|-----------|-------| +|`druid.indexer.logs.directory`|Local filesystem path.|log| #### S3 Task Logs -Store Task Logs in S3. +Store task logs in S3. |Property|Description|Default| |--------|-----------|-------| |`druid.indexer.logs.s3Bucket`|S3 bucket name.|none| |`druid.indexer.logs.s3Prefix`|S3 key prefix.|none| +#### Noop Task Logs + +No task logs are actually stored. + ### Firehose Module The Firehose module lists all available firehoses. There are no configurations. From 81ccb370199a8bcce7b777f948904ab29df01b94 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 12 Dec 2013 11:46:56 -0800 Subject: [PATCH 053/189] Indexing-Service.md: New overlord-related configs. --- docs/content/Indexing-Service.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/content/Indexing-Service.md b/docs/content/Indexing-Service.md index aed200a7c8f..0d1cc38621e 100644 --- a/docs/content/Indexing-Service.md +++ b/docs/content/Indexing-Service.md @@ -110,12 +110,16 @@ If autoscaling is enabled, new middle managers may be added when a task has been #### JVM Configuration -In addition to the configuration of some of the default modules in [Configuration](Configuration.html), the overlord module requires the following basic configs to run in remote mode: +In addition to the configuration of some of the default modules in [Configuration](Configuration.html), the overlord has the following basic configs: |Property|Description|Default| |--------|-----------|-------| |`druid.indexer.runner.type`|Choices "local" or "remote". Indicates whether tasks should be run locally or in a distributed environment.|local| -|`druid.indexer.storage.type`|Choices are "local" or "db". Indicates whether incoming tasks should be stored locally (in heap) or in a database. Storing incoming tasks in a database allows for tasks to be bootstrapped if the overlord should fail.|local| +|`druid.indexer.storage.type`|Choices are "local" or "db". Indicates whether incoming tasks should be stored locally (in heap) or in a database. Storing incoming tasks in a database allows for tasks to be resumed if the overlord should fail.|local| +|`druid.indexer.queue.maxSize`|Maximum number of active tasks at one time.|Integer.MAX_VALUE| +|`druid.indexer.queue.startDelay`|Sleep this long before starting overlord queue management. This can be useful to give a cluster time to re-orient itself after e.g. a widespread network issue.|PT1M| +|`druid.indexer.queue.restartDelay`|Sleep this long when overlord queue management throws an exception before trying again.|PT30S| +|`druid.indexer.queue.storageSyncRate`|Sync overlord state this often with an underlying task persistence mechanism.|PT1M| The following configs only apply if the overlord is running in remote mode: From be25d51a2c6b4ae45f4edca9faaca2e0b3f97a24 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 12 Dec 2013 13:40:23 -0800 Subject: [PATCH 054/189] RemoteTaskRunner: Fix issues leading to failing tests --- .../indexing/overlord/RemoteTaskRunner.java | 88 ++++++++++++++----- .../overlord/RemoteTaskRunnerTest.java | 17 ++-- 2 files changed, 73 insertions(+), 32 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java b/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java index 87bccea7cc9..8b823444319 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java @@ -32,6 +32,8 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.io.InputSupplier; import com.google.common.primitives.Ints; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.metamx.common.ISE; @@ -51,6 +53,7 @@ import io.druid.indexing.worker.TaskAnnouncement; import io.druid.indexing.worker.Worker; import io.druid.server.initialization.ZkPathsConfig; import io.druid.tasklogs.TaskLogStreamer; +import org.apache.commons.lang.mutable.MutableInt; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.cache.PathChildrenCache; import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; @@ -72,7 +75,6 @@ import java.util.TreeSet; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -153,7 +155,8 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer return; } - final CountDownLatch initialized = new CountDownLatch(1); + final MutableInt waitingFor = new MutableInt(1); + final Object waitingForMonitor = new Object(); // Add listener for creation/deletion of workers workerPathCache.getListenable().addListener( @@ -169,7 +172,32 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer event.getData().getData(), Worker.class ); - addWorker(worker); + synchronized (waitingForMonitor) { + waitingFor.increment(); + } + Futures.addCallback( + addWorker(worker), + new FutureCallback() + { + @Override + public void onSuccess(ZkWorker zkWorker) + { + synchronized (waitingForMonitor) { + waitingFor.decrement(); + waitingForMonitor.notifyAll(); + } + } + + @Override + public void onFailure(Throwable throwable) + { + synchronized (waitingForMonitor) { + waitingFor.decrement(); + waitingForMonitor.notifyAll(); + } + } + } + ); break; case CHILD_REMOVED: worker = jsonMapper.readValue( @@ -179,16 +207,22 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer removeWorker(worker); break; case INITIALIZED: - initialized.countDown(); + synchronized (waitingForMonitor) { + waitingFor.decrement(); + waitingForMonitor.notifyAll(); + } default: break; } } } ); - workerPathCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT); - initialized.await(); + synchronized (waitingForMonitor) { + while (waitingFor.intValue() > 0) { + waitingForMonitor.wait(); + } + } started = true; } catch (Exception e) { @@ -263,8 +297,11 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer @Override public ListenableFuture run(final Task task) { - final RemoteTaskRunnerWorkItem runningTask = runningTasks.get(task.getId()); - if (runningTask != null) { + final RemoteTaskRunnerWorkItem completeTask, runningTask, pendingTask; + if ((pendingTask = pendingTasks.get(task.getId())) != null) { + log.info("Assigned a task[%s] that is already pending, not doing anything", task.getId()); + return pendingTask.getResult(); + } else if ((runningTask = runningTasks.get(task.getId())) != null) { ZkWorker zkWorker = findWorkerRunningTask(task.getId()); if (zkWorker == null) { log.warn("Told to run task[%s], but no worker has started running it yet.", task.getId()); @@ -275,16 +312,12 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer taskComplete(runningTask, zkWorker, announcement.getTaskStatus()); } } - return runningTask.getResult(); + } else if ((completeTask = completeTasks.get(task.getId())) != null) { + return completeTask.getResult(); + } else { + return addPendingTask(task).getResult(); } - - RemoteTaskRunnerWorkItem pendingTask = pendingTasks.get(task.getId()); - if (pendingTask != null) { - log.info("Assigned a task[%s] that is already pending, not doing anything", task.getId()); - return pendingTask.getResult(); - } - return addPendingTask(task).getResult(); } /** @@ -573,14 +606,16 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer * The RemoteTaskRunner updates state according to these changes. * * @param worker contains metadata for a worker that has appeared in ZK + * @return future that will contain a fully initialized worker */ - private ZkWorker addWorker(final Worker worker) + private ListenableFuture addWorker(final Worker worker) { log.info("Worker[%s] reportin' for duty!", worker.getHost()); try { final String workerStatusPath = JOINER.join(zkPaths.getIndexerStatusPath(), worker.getHost()); final PathChildrenCache statusCache = pathChildrenCacheFactory.make(cf, workerStatusPath); + final SettableFuture retVal = SettableFuture.create(); final ZkWorker zkWorker = new ZkWorker( worker, statusCache, @@ -649,11 +684,18 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer } break; case INITIALIZED: - if (zkWorkers.putIfAbsent(worker.getHost(), zkWorker) != null) { - log.makeAlert("WTF?! Tried to add already-existing worker[%s]", worker.getHost()) + if (zkWorkers.putIfAbsent(worker.getHost(), zkWorker) == null) { + retVal.set(zkWorker); + } else { + final String message = String.format( + "WTF?! Tried to add already-existing worker[%s]", + worker.getHost() + ); + log.makeAlert(message) .addData("workerHost", worker.getHost()) .addData("workerIp", worker.getIp()) .emit(); + retVal.setException(new IllegalStateException(message)); } runPendingTasks(); } @@ -669,7 +711,7 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer } ); zkWorker.start(); - return zkWorker; + return retVal; } catch (Exception e) { throw Throwables.propagate(e); @@ -777,6 +819,12 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer Preconditions.checkNotNull(taskRunnerWorkItem, "taskRunnerWorkItem"); Preconditions.checkNotNull(zkWorker, "zkWorker"); Preconditions.checkNotNull(taskStatus, "taskStatus"); + log.info( + "Worker[%s] completed task[%s] with status[%s]", + zkWorker.getWorker().getHost(), + taskStatus.getId(), + taskStatus.getStatusCode() + ); // Worker is done with this task zkWorker.setLastCompletedTaskTime(new DateTime()); // Move from running -> complete diff --git a/indexing-service/src/test/java/io/druid/indexing/overlord/RemoteTaskRunnerTest.java b/indexing-service/src/test/java/io/druid/indexing/overlord/RemoteTaskRunnerTest.java index fc0b9fdb2e2..fcf9715fe62 100644 --- a/indexing-service/src/test/java/io/druid/indexing/overlord/RemoteTaskRunnerTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/overlord/RemoteTaskRunnerTest.java @@ -23,8 +23,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.api.client.repackaged.com.google.common.base.Throwables; import com.google.common.base.Function; import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.util.concurrent.ListenableFuture; import com.metamx.emitter.EmittingLogger; @@ -55,7 +55,6 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import java.util.Arrays; import java.util.Set; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicReference; @@ -303,16 +302,13 @@ public class RemoteTaskRunnerTest doSetup(); - Set existingTasks = Sets.newHashSet(); + final Set existingTasks = Sets.newHashSet(); for (ZkWorker zkWorker : remoteTaskRunner.getWorkers()) { existingTasks.addAll(zkWorker.getRunningTasks().keySet()); } + Assert.assertEquals("existingTasks", ImmutableSet.of("first", "second"), existingTasks); - Assert.assertTrue(existingTasks.size() == 2); - Assert.assertTrue(existingTasks.contains("first")); - Assert.assertTrue(existingTasks.contains("second")); - - Set runningTasks = Sets.newHashSet( + final Set runningTasks = Sets.newHashSet( Iterables.transform( remoteTaskRunner.getRunningTasks(), new Function() @@ -325,10 +321,7 @@ public class RemoteTaskRunnerTest } ) ); - - Assert.assertTrue(runningTasks.size() == 1); - Assert.assertTrue(runningTasks.contains("second")); - Assert.assertFalse(runningTasks.contains("first")); + Assert.assertEquals("runningTasks", ImmutableSet.of("first", "second"), runningTasks); } @Test From ba757b1e5a1cbfc6e54e9c0460bd49dc2b234bd2 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 12 Dec 2013 13:50:53 -0800 Subject: [PATCH 055/189] IndexTask: Actually make and publish segments for the correct intervals. --- .../main/java/io/druid/indexing/common/task/IndexTask.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java index 24aa69c7bf0..19cb791b77e 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java @@ -41,6 +41,7 @@ import io.druid.indexer.granularity.GranularitySpec; import io.druid.indexing.common.TaskLock; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.TaskToolbox; +import io.druid.indexing.common.actions.SegmentInsertAction; import io.druid.indexing.common.index.YeOldePlumberSchool; import io.druid.query.aggregation.AggregatorFactory; import io.druid.segment.loading.DataSegmentPusher; @@ -149,12 +150,13 @@ public class IndexTask extends AbstractFixedIntervalTask indexGranularity, shardSpec ), - getInterval(), + bucket, myLock.getVersion() ); segments.add(segment); } } + toolbox.getTaskActionClient().submit(new SegmentInsertAction(segments)); return TaskStatus.success(getId()); } From 169f149cf9636c26f1a58e1f9e5dc1067cb7a994 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 12 Dec 2013 13:51:13 -0800 Subject: [PATCH 056/189] TaskLifecycleTest: Fix broken setUp and broken assumptions. --- .../indexing/overlord/ThreadPoolTaskRunner.java | 3 ++- .../druid/indexing/overlord/TaskLifecycleTest.java | 12 +++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/ThreadPoolTaskRunner.java b/indexing-service/src/main/java/io/druid/indexing/overlord/ThreadPoolTaskRunner.java index a4a8db0d1a3..2cc94ac7400 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/ThreadPoolTaskRunner.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/ThreadPoolTaskRunner.java @@ -19,6 +19,7 @@ package io.druid.indexing.overlord; +import com.google.api.client.repackaged.com.google.common.base.Preconditions; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; @@ -65,7 +66,7 @@ public class ThreadPoolTaskRunner implements TaskRunner, QuerySegmentWalker TaskToolboxFactory toolboxFactory ) { - this.toolboxFactory = toolboxFactory; + this.toolboxFactory = Preconditions.checkNotNull(toolboxFactory, "toolboxFactory"); this.exec = MoreExecutors.listeningDecorator(Execs.singleThreaded("task-runner-%d")); } diff --git a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java index 16d2421e761..ac90c1e12f0 100644 --- a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java @@ -119,10 +119,8 @@ public class TaskLifecycleTest ts = new HeapMemoryTaskStorage(); tsqa = new TaskStorageQueryAdapter(ts); tl = new TaskLockbox(ts); - tac = new LocalTaskActionClientFactory(ts, new TaskActionToolbox(tl, mdc, newMockEmitter())); - tr = new ThreadPoolTaskRunner(tb); - tq = new TaskQueue(tqc, ts, tr, tac, tl, emitter); mdc = newMockMDC(); + tac = new LocalTaskActionClientFactory(ts, new TaskActionToolbox(tl, mdc, newMockEmitter())); tb = new TaskToolboxFactory( new TaskConfig(tmp.toString(), null, null, 50000), tac, @@ -173,6 +171,8 @@ public class TaskLifecycleTest ), new DefaultObjectMapper() ); + tr = new ThreadPoolTaskRunner(tb); + tq = new TaskQueue(tqc, ts, tr, tac, tl, emitter); tq.start(); } @@ -258,11 +258,9 @@ public class TaskLifecycleTest -1 ); - final TaskStatus mergedStatus = runTask(indexTask); - final TaskStatus status = ts.getStatus(indexTask.getId()).get(); + final TaskStatus status = runTask(indexTask); - Assert.assertEquals("statusCode", TaskStatus.Status.SUCCESS, status.getStatusCode()); - Assert.assertEquals("merged statusCode", TaskStatus.Status.FAILED, mergedStatus.getStatusCode()); + Assert.assertEquals("statusCode", TaskStatus.Status.FAILED, status.getStatusCode()); Assert.assertEquals("num segments published", 0, mdc.getPublished().size()); Assert.assertEquals("num segments nuked", 0, mdc.getNuked().size()); } From 370e2f855a784706ceb382259e18b7acfc8acf80 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 12 Dec 2013 13:58:03 -0800 Subject: [PATCH 057/189] TaskSerdeTest: Fix IndexTask test by including an actual firehoseFactory --- .../io/druid/indexing/common/task/TaskSerdeTest.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/indexing-service/src/test/java/io/druid/indexing/common/task/TaskSerdeTest.java b/indexing-service/src/test/java/io/druid/indexing/common/task/TaskSerdeTest.java index 3d2598ffaa4..485b3202b54 100644 --- a/indexing-service/src/test/java/io/druid/indexing/common/task/TaskSerdeTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/common/task/TaskSerdeTest.java @@ -19,6 +19,7 @@ package io.druid.indexing.common.task; +import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -26,6 +27,7 @@ import com.metamx.common.Granularity; import io.druid.data.input.impl.JSONDataSpec; import io.druid.data.input.impl.TimestampSpec; import io.druid.granularity.QueryGranularity; +import io.druid.guice.FirehoseModule; import io.druid.indexer.HadoopDruidIndexerSchema; import io.druid.indexer.granularity.UniformGranularitySpec; import io.druid.indexer.rollup.DataRollupSpec; @@ -35,6 +37,7 @@ import io.druid.query.aggregation.CountAggregatorFactory; import io.druid.query.aggregation.DoubleSumAggregatorFactory; import io.druid.segment.IndexGranularity; import io.druid.segment.realtime.Schema; +import io.druid.segment.realtime.firehose.LocalFirehoseFactory; import io.druid.timeline.DataSegment; import io.druid.timeline.partition.NoneShardSpec; import junit.framework.Assert; @@ -42,6 +45,8 @@ import org.joda.time.Interval; import org.joda.time.Period; import org.junit.Test; +import java.io.File; + public class TaskSerdeTest { @Test @@ -55,11 +60,14 @@ public class TaskSerdeTest new AggregatorFactory[]{new DoubleSumAggregatorFactory("met", "met")}, QueryGranularity.NONE, 10000, - null, + new LocalFirehoseFactory(new File("lol"), "rofl", null), -1 ); final ObjectMapper jsonMapper = new DefaultObjectMapper(); + for (final Module jacksonModule : new FirehoseModule().getJacksonModules()) { + jsonMapper.registerModule(jacksonModule); + } final String json = jsonMapper.writeValueAsString(task); Thread.sleep(100); // Just want to run the clock a bit to make sure the task id doesn't change @@ -72,6 +80,8 @@ public class TaskSerdeTest Assert.assertEquals(task.getGroupId(), task2.getGroupId()); Assert.assertEquals(task.getDataSource(), task2.getDataSource()); Assert.assertEquals(task.getInterval(), task2.getInterval()); + Assert.assertTrue(task.getFirehoseFactory() instanceof LocalFirehoseFactory); + Assert.assertTrue(task2.getFirehoseFactory() instanceof LocalFirehoseFactory); } @Test From 70c153592f049cb7d975bc255b42a4f12f547819 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 12 Dec 2013 14:16:13 -0800 Subject: [PATCH 058/189] CliPeon: Fix local mode --- .../io/druid/indexing/common/task/Task.java | 2 + .../worker/executor/ExecutorLifecycle.java | 65 ++++++++++++------- .../src/main/java/io/druid/cli/CliPeon.java | 1 - 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/Task.java b/indexing-service/src/main/java/io/druid/indexing/common/task/Task.java index df9abe826c8..4d6afd2ebf6 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/Task.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/Task.java @@ -100,6 +100,8 @@ public interface Task * actions must be idempotent, since this method may be executed multiple times. This typically runs on the * coordinator. If this method throws an exception, the task should be considered a failure. * + * This method must be idempotent, as it may be run multiple times per task. + * * @param taskActionClient action client for this task (not the full toolbox) * * @return true if ready, false if not ready yet diff --git a/indexing-service/src/main/java/io/druid/indexing/worker/executor/ExecutorLifecycle.java b/indexing-service/src/main/java/io/druid/indexing/worker/executor/ExecutorLifecycle.java index bc8879b6960..17d889b5143 100644 --- a/indexing-service/src/main/java/io/druid/indexing/worker/executor/ExecutorLifecycle.java +++ b/indexing-service/src/main/java/io/druid/indexing/worker/executor/ExecutorLifecycle.java @@ -20,16 +20,19 @@ package io.druid.indexing.worker.executor; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.api.client.repackaged.com.google.common.base.Preconditions; import com.google.common.base.Function; import com.google.common.base.Throwables; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.inject.Inject; +import com.metamx.common.ISE; import com.metamx.common.lifecycle.LifecycleStart; import com.metamx.common.lifecycle.LifecycleStop; import com.metamx.emitter.EmittingLogger; import io.druid.concurrent.Execs; import io.druid.indexing.common.TaskStatus; +import io.druid.indexing.common.actions.TaskActionClientFactory; import io.druid.indexing.common.task.Task; import io.druid.indexing.overlord.TaskRunner; @@ -47,6 +50,7 @@ public class ExecutorLifecycle private static final EmittingLogger log = new EmittingLogger(ExecutorLifecycle.class); private final ExecutorLifecycleConfig config; + private final TaskActionClientFactory taskActionClientFactory; private final TaskRunner taskRunner; private final ObjectMapper jsonMapper; @@ -57,11 +61,13 @@ public class ExecutorLifecycle @Inject public ExecutorLifecycle( ExecutorLifecycleConfig config, + TaskActionClientFactory taskActionClientFactory, TaskRunner taskRunner, ObjectMapper jsonMapper ) { this.config = config; + this.taskActionClientFactory = taskActionClientFactory; this.taskRunner = taskRunner; this.jsonMapper = jsonMapper; } @@ -69,9 +75,9 @@ public class ExecutorLifecycle @LifecycleStart public void start() { - final File taskFile = config.getTaskFile(); - final File statusFile = config.getStatusFile(); - final InputStream parentStream = config.getParentStream(); + final File taskFile = Preconditions.checkNotNull(config.getTaskFile(), "taskFile"); + final File statusFile = Preconditions.checkNotNull(config.getStatusFile(), "statusFile"); + final InputStream parentStream = Preconditions.checkNotNull(config.getParentStream(), "parentStream"); final Task task; @@ -111,28 +117,41 @@ public class ExecutorLifecycle } ); - statusFuture = Futures.transform( - taskRunner.run(task), new Function() - { - @Override - public TaskStatus apply(TaskStatus taskStatus) - { - try { - log.info( - "Task completed with status: %s", - jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(taskStatus) - ); - - statusFile.getParentFile().mkdirs(); - jsonMapper.writeValue(statusFile, taskStatus); - - return taskStatus; - } - catch (Exception e) { - throw Throwables.propagate(e); - } + // Won't hurt in remote mode, and is required for setting up locks in local mode: + try { + if (!task.isReady(taskActionClientFactory.create(task))) { + throw new ISE("Task is not ready to run yet!", task.getId()); } + } catch (Exception e) { + throw new ISE(e, "Failed to run isReady", task.getId()); } + + statusFuture = Futures.transform( + taskRunner.run(task), + new Function() + { + @Override + public TaskStatus apply(TaskStatus taskStatus) + { + try { + log.info( + "Task completed with status: %s", + jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(taskStatus) + ); + + final File statusFileParent = statusFile.getParentFile(); + if (statusFileParent != null) { + statusFileParent.mkdirs(); + } + jsonMapper.writeValue(statusFile, taskStatus); + + return taskStatus; + } + catch (Exception e) { + throw Throwables.propagate(e); + } + } + } ); } diff --git a/services/src/main/java/io/druid/cli/CliPeon.java b/services/src/main/java/io/druid/cli/CliPeon.java index 2c1b4511193..e8a5b985bd4 100644 --- a/services/src/main/java/io/druid/cli/CliPeon.java +++ b/services/src/main/java/io/druid/cli/CliPeon.java @@ -53,7 +53,6 @@ import io.druid.indexing.common.index.NoopChatHandlerProvider; import io.druid.indexing.common.index.ServiceAnnouncingChatHandlerProvider; import io.druid.indexing.overlord.HeapMemoryTaskStorage; import io.druid.indexing.overlord.IndexerDBCoordinator; -import io.druid.indexing.overlord.TaskQueue; import io.druid.indexing.overlord.TaskRunner; import io.druid.indexing.overlord.TaskStorage; import io.druid.indexing.overlord.ThreadPoolTaskRunner; From a58f90a6894ddc84af1002ee8ee803a291ac9f0f Mon Sep 17 00:00:00 2001 From: fjy Date: Thu, 12 Dec 2013 14:51:14 -0800 Subject: [PATCH 059/189] prepare for next release --- build.sh | 2 +- docs/content/Booting-a-production-cluster.md | 2 +- docs/content/Examples.md | 4 +- docs/content/Realtime.md | 2 +- .../Tutorial:-A-First-Look-at-Druid.md | 4 +- .../Tutorial:-Loading-Your-Data-Part-1.md | 15 +++++ .../Tutorial:-Loading-Your-Data-Part-2.md | 2 +- docs/content/Tutorial:-The-Druid-Cluster.md | 6 +- docs/content/Tutorial:-Webstream.md | 4 +- docs/content/Twitter-Tutorial.textile | 2 +- docs/content/toc.textile | 2 +- examples/config/historical/runtime.properties | 2 +- examples/config/realtime/runtime.properties | 2 +- kafka-eight/pom.xml | 60 +------------------ .../src/main/java/io/druid/cli/CliBroker.java | 2 +- .../java/io/druid/cli/CliCoordinator.java | 2 +- .../java/io/druid/cli/CliHadoopIndexer.java | 2 +- .../main/java/io/druid/cli/CliHistorical.java | 2 +- .../main/java/io/druid/cli/CliOverlord.java | 2 +- .../main/java/io/druid/cli/CliRealtime.java | 2 +- .../java/io/druid/cli/CliRealtimeExample.java | 2 +- 21 files changed, 41 insertions(+), 82 deletions(-) diff --git a/build.sh b/build.sh index ea05e4af3bd..4053d6c1cdc 100755 --- a/build.sh +++ b/build.sh @@ -30,4 +30,4 @@ echo "For examples, see: " echo " " ls -1 examples/*/*sh echo " " -echo "See also http://druid.io/docs/0.6.30" +echo "See also http://druid.io/docs/0.6.31" diff --git a/docs/content/Booting-a-production-cluster.md b/docs/content/Booting-a-production-cluster.md index 4405944b8b9..aeea7c47988 100644 --- a/docs/content/Booting-a-production-cluster.md +++ b/docs/content/Booting-a-production-cluster.md @@ -3,7 +3,7 @@ layout: doc_page --- # Booting a Single Node Cluster # -[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.30-bin.tar.gz). +[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.31-bin.tar.gz). The [ec2 run script](https://github.com/metamx/druid/blob/master/examples/bin/run_ec2.sh), run_ec2.sh, is located at 'examples/bin' if you have checked out the code, or at the root of the project if you've downloaded a tarball. The scripts rely on the [Amazon EC2 API Tools](http://aws.amazon.com/developertools/351), and you will need to set three environment variables: diff --git a/docs/content/Examples.md b/docs/content/Examples.md index e4d3ea6f6f7..4365a424619 100644 --- a/docs/content/Examples.md +++ b/docs/content/Examples.md @@ -19,13 +19,13 @@ Clone Druid and build it: git clone https://github.com/metamx/druid.git druid cd druid git fetch --tags -git checkout druid-0.6.30 +git checkout druid-0.6.31 ./build.sh ``` ### Downloading the DSK (Druid Standalone Kit) -[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.30-bin.tar.gz) a stand-alone tarball and run it: +[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.31-bin.tar.gz) a stand-alone tarball and run it: ``` bash tar -xzf druid-services-0.X.X-bin.tar.gz diff --git a/docs/content/Realtime.md b/docs/content/Realtime.md index aaaff7c7840..13c56860344 100644 --- a/docs/content/Realtime.md +++ b/docs/content/Realtime.md @@ -27,7 +27,7 @@ druid.host=localhost druid.service=realtime druid.port=8083 -druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.30"] +druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.31"] druid.zk.service.host=localhost diff --git a/docs/content/Tutorial:-A-First-Look-at-Druid.md b/docs/content/Tutorial:-A-First-Look-at-Druid.md index 9703918e7df..b276378fd5b 100644 --- a/docs/content/Tutorial:-A-First-Look-at-Druid.md +++ b/docs/content/Tutorial:-A-First-Look-at-Druid.md @@ -49,7 +49,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu ### Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.30-bin.tar.gz). Download this file to a directory of your choosing. +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.31-bin.tar.gz). Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -60,7 +60,7 @@ tar -zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.30 +cd druid-services-0.6.31 ``` You should see a bunch of files: diff --git a/docs/content/Tutorial:-Loading-Your-Data-Part-1.md b/docs/content/Tutorial:-Loading-Your-Data-Part-1.md index 100df976de4..2c56c81a839 100644 --- a/docs/content/Tutorial:-Loading-Your-Data-Part-1.md +++ b/docs/content/Tutorial:-Loading-Your-Data-Part-1.md @@ -246,6 +246,21 @@ Issuing a [TimeBoundaryQuery](TimeBoundaryQuery.html) should yield: } ] ``` +Problems? +--------- + +If you decide to reuse the local firehose to ingest your own data and if you run into problems, you can read the individual task logs at: + +```bash +/log/.log + +``` + +One thing to note is that the log file will only exist once the task completes with either SUCCESS or FAILURE. +Task logs can be stored locally or uploaded to [Deep Storage](Deep-Storage.html). More information about how to configure this is [here](Configuration.html). + +Most common data ingestion problems are around timestamp formats and other malformed data issues. + Next Steps ---------- diff --git a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md index a6eecc14f0a..2ceec4f209a 100644 --- a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md +++ b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md @@ -44,7 +44,7 @@ With real-world data, we recommend having a message bus such as [Apache Kafka](h #### Setting up Kafka -[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.30/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. +[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.31/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. Instructions for booting a Zookeeper and then Kafka cluster are available [here](http://kafka.apache.org/07/quickstart.html). diff --git a/docs/content/Tutorial:-The-Druid-Cluster.md b/docs/content/Tutorial:-The-Druid-Cluster.md index f084e0efdc6..d30c65a8a8b 100644 --- a/docs/content/Tutorial:-The-Druid-Cluster.md +++ b/docs/content/Tutorial:-The-Druid-Cluster.md @@ -13,7 +13,7 @@ In this tutorial, we will set up other types of Druid nodes as well as and exter If you followed the first tutorial, you should already have Druid downloaded. If not, let's go back and do that first. -You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.30-bin.tar.gz) +You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.31-bin.tar.gz) and untar the contents within by issuing: @@ -149,7 +149,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.30"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.31"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b @@ -238,7 +238,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.30","io.druid.extensions:druid-kafka-seven:0.6.30"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.31","io.druid.extensions:druid-kafka-seven:0.6.31"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/docs/content/Tutorial:-Webstream.md b/docs/content/Tutorial:-Webstream.md index 97987247b00..007feaea850 100644 --- a/docs/content/Tutorial:-Webstream.md +++ b/docs/content/Tutorial:-Webstream.md @@ -37,7 +37,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.30-bin.tar.gz) +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.31-bin.tar.gz) Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -48,7 +48,7 @@ tar zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.30 +cd druid-services-0.6.31 ``` You should see a bunch of files: diff --git a/docs/content/Twitter-Tutorial.textile b/docs/content/Twitter-Tutorial.textile index 1fbe5b3c038..cf2282b354c 100644 --- a/docs/content/Twitter-Tutorial.textile +++ b/docs/content/Twitter-Tutorial.textile @@ -9,7 +9,7 @@ There are two ways to setup Druid: download a tarball, or build it from source. h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.30-bin.tar.gz. +We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.31-bin.tar.gz. Download this bad boy to a directory of your choosing. You can extract the awesomeness within by issuing: diff --git a/docs/content/toc.textile b/docs/content/toc.textile index ae0da14bcc7..802d94ef434 100644 --- a/docs/content/toc.textile +++ b/docs/content/toc.textile @@ -15,9 +15,9 @@ h2. Getting Started * "Tutorial: All About Queries":./Tutorial:-All-About-Queries.html h2. Operations -* "Cluster Setup":./Cluster-setup.html * "Configuration":Configuration.html * "Extending Druid":./Modules.html +* "Cluster Setup":./Cluster-setup.html * "Booting a Production Cluster":./Booting-a-production-cluster.html h2. Data Ingestion diff --git a/examples/config/historical/runtime.properties b/examples/config/historical/runtime.properties index e8ec335789e..b4344b8e775 100644 --- a/examples/config/historical/runtime.properties +++ b/examples/config/historical/runtime.properties @@ -4,7 +4,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.30"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.31"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b diff --git a/examples/config/realtime/runtime.properties b/examples/config/realtime/runtime.properties index b694b40dd58..9303f37165a 100644 --- a/examples/config/realtime/runtime.properties +++ b/examples/config/realtime/runtime.properties @@ -4,7 +4,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.30","io.druid.extensions:druid-kafka-seven:0.6.30","io.druid.extensions:druid-rabbitmq:0.6.30"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.31","io.druid.extensions:druid-kafka-seven:0.6.31","io.druid.extensions:druid-rabbitmq:0.6.31"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index c4ac5339f5e..e5c769ec2dd 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -18,7 +18,8 @@ ~ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --> - + 4.0.0 io.druid.extensions druid-kafka-eight @@ -32,8 +33,6 @@ - - io.druid druid-api @@ -54,61 +53,6 @@ - - - org.scala-lang - scala-library - 2.9.2 - - - net.sf.jopt-simple - jopt-simple - 3.2 - - - org.slf4j - slf4j-simple - 1.6.4 - - - org.scala-lang - scala-compiler - 2.9.2 - - - com.101tec - zkclient - 0.3 - - - org.xerial.snappy - snappy-java - 1.0.4.1 - - - com.yammer.metrics - metrics-core - 2.2.0 - - - com.yammer.metrics - metrics-annotation - 2.2.0 - - - org.easymock - easymock - 3.0 - test - - - org.scalatest - scalatest_2.9.2 - 1.8 - test - - - junit diff --git a/services/src/main/java/io/druid/cli/CliBroker.java b/services/src/main/java/io/druid/cli/CliBroker.java index 41ddbbc9b03..b5fc025af24 100644 --- a/services/src/main/java/io/druid/cli/CliBroker.java +++ b/services/src/main/java/io/druid/cli/CliBroker.java @@ -53,7 +53,7 @@ import java.util.List; */ @Command( name = "broker", - description = "Runs a broker node, see http://druid.io/docs/0.6.30/Broker.html for a description" + description = "Runs a broker node, see http://druid.io/docs/0.6.31/Broker.html for a description" ) public class CliBroker extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliCoordinator.java b/services/src/main/java/io/druid/cli/CliCoordinator.java index 013c2c59d91..f12d1f893e8 100644 --- a/services/src/main/java/io/druid/cli/CliCoordinator.java +++ b/services/src/main/java/io/druid/cli/CliCoordinator.java @@ -63,7 +63,7 @@ import java.util.List; */ @Command( name = "coordinator", - description = "Runs the Coordinator, see http://druid.io/docs/0.6.30/Coordinator.html for a description." + description = "Runs the Coordinator, see http://druid.io/docs/0.6.31/Coordinator.html for a description." ) public class CliCoordinator extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java index 1fb6ef7df99..bb0214ca26f 100644 --- a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java +++ b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java @@ -41,7 +41,7 @@ import java.util.List; */ @Command( name = "hadoop", - description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.30/Batch-ingestion.html for a description." + description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.31/Batch-ingestion.html for a description." ) public class CliHadoopIndexer implements Runnable { diff --git a/services/src/main/java/io/druid/cli/CliHistorical.java b/services/src/main/java/io/druid/cli/CliHistorical.java index 0d2b75ed62d..0adf699140e 100644 --- a/services/src/main/java/io/druid/cli/CliHistorical.java +++ b/services/src/main/java/io/druid/cli/CliHistorical.java @@ -42,7 +42,7 @@ import java.util.List; */ @Command( name = "historical", - description = "Runs a Historical node, see http://druid.io/docs/0.6.30/Historical.html for a description" + description = "Runs a Historical node, see http://druid.io/docs/0.6.31/Historical.html for a description" ) public class CliHistorical extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index 18609f7aea6..b4b2fa4071a 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -95,7 +95,7 @@ import java.util.List; */ @Command( name = "overlord", - description = "Runs an Overlord node, see http://druid.io/docs/0.6.30/Indexing-Service.html for a description" + description = "Runs an Overlord node, see http://druid.io/docs/0.6.31/Indexing-Service.html for a description" ) public class CliOverlord extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtime.java b/services/src/main/java/io/druid/cli/CliRealtime.java index 1765451e5e8..89452ed5c23 100644 --- a/services/src/main/java/io/druid/cli/CliRealtime.java +++ b/services/src/main/java/io/druid/cli/CliRealtime.java @@ -30,7 +30,7 @@ import java.util.List; */ @Command( name = "realtime", - description = "Runs a realtime node, see http://druid.io/docs/0.6.30/Realtime.html for a description" + description = "Runs a realtime node, see http://druid.io/docs/0.6.31/Realtime.html for a description" ) public class CliRealtime extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtimeExample.java b/services/src/main/java/io/druid/cli/CliRealtimeExample.java index 7a0abc2fc55..dc7725f575b 100644 --- a/services/src/main/java/io/druid/cli/CliRealtimeExample.java +++ b/services/src/main/java/io/druid/cli/CliRealtimeExample.java @@ -42,7 +42,7 @@ import java.util.concurrent.Executor; */ @Command( name = "realtime", - description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.30/Realtime.html for a description" + description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.31/Realtime.html for a description" ) public class CliRealtimeExample extends ServerRunnable { From 365da245c21925a6c20d6316d6409909020ffa10 Mon Sep 17 00:00:00 2001 From: fjy Date: Thu, 12 Dec 2013 14:52:57 -0800 Subject: [PATCH 060/189] [maven-release-plugin] prepare release druid-0.6.31 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 5 ++--- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 16 insertions(+), 17 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 72dd5923d2f..a4775795563 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31-SNAPSHOT + 0.6.31 diff --git a/common/pom.xml b/common/pom.xml index 3337214b3c4..40dfea76992 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31-SNAPSHOT + 0.6.31 diff --git a/examples/pom.xml b/examples/pom.xml index 563aff36a2b..acfb97be4be 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31-SNAPSHOT + 0.6.31 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 0e0ca99990b..ff197769ab0 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31-SNAPSHOT + 0.6.31 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 261c5e2d4ec..b8bb14bd784 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31-SNAPSHOT + 0.6.31 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 4f3433cae3d..e11669f15a5 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31-SNAPSHOT + 0.6.31 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index e5c769ec2dd..b2e2a6a74fd 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -18,8 +18,7 @@ ~ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --> - + 4.0.0 io.druid.extensions druid-kafka-eight @@ -29,7 +28,7 @@ io.druid druid - 0.6.31-SNAPSHOT + 0.6.31 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index ad722b50cdd..5a6a67e10e5 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31-SNAPSHOT + 0.6.31 diff --git a/pom.xml b/pom.xml index db67db72a75..fb9d854e1c1 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.31-SNAPSHOT + 0.6.31 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.31 diff --git a/processing/pom.xml b/processing/pom.xml index f4bbe0ccd54..38f0f0f7415 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31-SNAPSHOT + 0.6.31 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index c8f5963ebba..6a792d87452 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.31-SNAPSHOT + 0.6.31 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 24f82396316..faa8558963d 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31-SNAPSHOT + 0.6.31 diff --git a/server/pom.xml b/server/pom.xml index e05f8fd7b15..cf8f417c037 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31-SNAPSHOT + 0.6.31 diff --git a/services/pom.xml b/services/pom.xml index e0f8975f7e5..91b25883102 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.31-SNAPSHOT + 0.6.31 From 7c744ffda0038fb342246ae6492a2b4b357b9acb Mon Sep 17 00:00:00 2001 From: fjy Date: Thu, 12 Dec 2013 14:53:01 -0800 Subject: [PATCH 061/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index a4775795563..66432acbfa3 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31 + 0.6.32-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index 40dfea76992..efb65548359 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31 + 0.6.32-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index acfb97be4be..4473c3704cf 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31 + 0.6.32-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index ff197769ab0..e0769a73a7b 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31 + 0.6.32-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index b8bb14bd784..20be3ac56e1 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31 + 0.6.32-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index e11669f15a5..5c73a020c75 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31 + 0.6.32-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index b2e2a6a74fd..f26fbc2a42b 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31 + 0.6.32-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 5a6a67e10e5..750f2bb9b4e 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31 + 0.6.32-SNAPSHOT diff --git a/pom.xml b/pom.xml index fb9d854e1c1..c391e4a43e2 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.31 + 0.6.32-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.31 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index 38f0f0f7415..1ffb41c5e2f 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31 + 0.6.32-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 6a792d87452..14e0d426797 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.31 + 0.6.32-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index faa8558963d..bcc304c2405 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31 + 0.6.32-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index cf8f417c037..41eb69b15a8 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.31 + 0.6.32-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index 91b25883102..9ba368a0b17 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.31 + 0.6.32-SNAPSHOT From dbe93034d0d3754dae233149841662c8626bfdee Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 12 Dec 2013 17:19:07 -0800 Subject: [PATCH 062/189] StatusResource: Remove extension versions They cause services to create a brand-new startup injector and reload all modules whenever /status is requested. Not sure how bad this is, but I think we're only supposed to have one startup injector so there may be a better approach out there. --- .../java/io/druid/server/StatusResource.java | 61 +------------------ 1 file changed, 3 insertions(+), 58 deletions(-) diff --git a/server/src/main/java/io/druid/server/StatusResource.java b/server/src/main/java/io/druid/server/StatusResource.java index f2c01050496..d6e5371f0c3 100644 --- a/server/src/main/java/io/druid/server/StatusResource.java +++ b/server/src/main/java/io/druid/server/StatusResource.java @@ -20,16 +20,11 @@ package io.druid.server; import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.inject.Injector; -import io.druid.initialization.DruidModule; import io.druid.initialization.Initialization; -import io.druid.server.initialization.ExtensionsConfig; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; -import java.util.ArrayList; -import java.util.List; /** */ @@ -47,52 +42,20 @@ public class StatusResource { return new Status( Initialization.class.getPackage().getImplementationVersion(), - getExtensionVersions(), new Memory(Runtime.getRuntime()) ); } - /** - * Load the unique extensions and return their implementation-versions - * - * @return map of extensions loaded with their respective implementation versions. - */ - private static List getExtensionVersions() - { - final Injector injector = Initialization.makeStartupInjector(); - final ExtensionsConfig config = injector.getInstance(ExtensionsConfig.class); - final List druidModules = Initialization.getFromExtensions(config, DruidModule.class); - - List moduleVersions = new ArrayList<>(); - for (DruidModule module : druidModules) { - - String artifact = module.getClass().getPackage().getImplementationTitle(); - String version = module.getClass().getPackage().getImplementationVersion(); - - ModuleVersion moduleVersion; - if (artifact != null) { - moduleVersion = new ModuleVersion(module.getClass().getCanonicalName(), artifact, version); - } else { - moduleVersion = new ModuleVersion(module.getClass().getCanonicalName()); - } - - moduleVersions.add(moduleVersion); - } - return moduleVersions; - } - public static class Status { final String version; - final List modules; final Memory memory; public Status( - String version, List modules, Memory memory + String version, Memory memory ) { this.version = version; - this.modules = modules; this.memory = memory; } @@ -102,12 +65,6 @@ public class StatusResource return version; } - @JsonProperty - public List getModules() - { - return modules; - } - @JsonProperty public Memory getMemory() { @@ -117,20 +74,8 @@ public class StatusResource @Override public String toString() { - final String NL = "\n"; - StringBuilder output = new StringBuilder(); - output.append(String.format("Druid version - %s", version)).append(NL).append(NL); - - if (modules.size() > 0) { - output.append("Registered Druid Modules").append(NL); - } else { - output.append("No Druid Modules loaded !"); - } - - for (ModuleVersion moduleVersion : modules) { - output.append(moduleVersion).append(NL); - } - return output.toString(); + final String NL = System.getProperty("line.separator"); + return String.format("Druid version - %s", version) + NL; } } From a8830e6bfd243aff479e451215d1bf6da4b13e51 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 12 Dec 2013 17:23:24 -0800 Subject: [PATCH 063/189] Update druid-api --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c391e4a43e2..6f4b68f5538 100644 --- a/pom.xml +++ b/pom.xml @@ -41,7 +41,7 @@ UTF-8 0.25.1 2.1.0-incubating - 0.1.5 + 0.1.6 From 19eafe0e96ac4354ef6f9fa0719b076839c6650e Mon Sep 17 00:00:00 2001 From: fjy Date: Thu, 12 Dec 2013 17:26:50 -0800 Subject: [PATCH 064/189] prepare for next release --- build.sh | 2 +- docs/content/Booting-a-production-cluster.md | 2 +- docs/content/Examples.md | 4 ++-- docs/content/Realtime.md | 2 +- docs/content/Tutorial:-A-First-Look-at-Druid.md | 4 ++-- docs/content/Tutorial:-Loading-Your-Data-Part-2.md | 2 +- docs/content/Tutorial:-The-Druid-Cluster.md | 6 +++--- docs/content/Tutorial:-Webstream.md | 4 ++-- docs/content/Twitter-Tutorial.textile | 2 +- examples/config/historical/runtime.properties | 2 +- examples/config/realtime/runtime.properties | 2 +- services/src/main/java/io/druid/cli/CliBroker.java | 2 +- services/src/main/java/io/druid/cli/CliCoordinator.java | 2 +- services/src/main/java/io/druid/cli/CliHadoopIndexer.java | 2 +- services/src/main/java/io/druid/cli/CliHistorical.java | 2 +- services/src/main/java/io/druid/cli/CliOverlord.java | 2 +- services/src/main/java/io/druid/cli/CliRealtime.java | 2 +- services/src/main/java/io/druid/cli/CliRealtimeExample.java | 2 +- 18 files changed, 23 insertions(+), 23 deletions(-) diff --git a/build.sh b/build.sh index 4053d6c1cdc..f5f420383fb 100755 --- a/build.sh +++ b/build.sh @@ -30,4 +30,4 @@ echo "For examples, see: " echo " " ls -1 examples/*/*sh echo " " -echo "See also http://druid.io/docs/0.6.31" +echo "See also http://druid.io/docs/0.6.32" diff --git a/docs/content/Booting-a-production-cluster.md b/docs/content/Booting-a-production-cluster.md index aeea7c47988..80c51be4120 100644 --- a/docs/content/Booting-a-production-cluster.md +++ b/docs/content/Booting-a-production-cluster.md @@ -3,7 +3,7 @@ layout: doc_page --- # Booting a Single Node Cluster # -[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.31-bin.tar.gz). +[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.32-bin.tar.gz). The [ec2 run script](https://github.com/metamx/druid/blob/master/examples/bin/run_ec2.sh), run_ec2.sh, is located at 'examples/bin' if you have checked out the code, or at the root of the project if you've downloaded a tarball. The scripts rely on the [Amazon EC2 API Tools](http://aws.amazon.com/developertools/351), and you will need to set three environment variables: diff --git a/docs/content/Examples.md b/docs/content/Examples.md index 4365a424619..b372aac929a 100644 --- a/docs/content/Examples.md +++ b/docs/content/Examples.md @@ -19,13 +19,13 @@ Clone Druid and build it: git clone https://github.com/metamx/druid.git druid cd druid git fetch --tags -git checkout druid-0.6.31 +git checkout druid-0.6.32 ./build.sh ``` ### Downloading the DSK (Druid Standalone Kit) -[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.31-bin.tar.gz) a stand-alone tarball and run it: +[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.32-bin.tar.gz) a stand-alone tarball and run it: ``` bash tar -xzf druid-services-0.X.X-bin.tar.gz diff --git a/docs/content/Realtime.md b/docs/content/Realtime.md index 13c56860344..23d66bb78e9 100644 --- a/docs/content/Realtime.md +++ b/docs/content/Realtime.md @@ -27,7 +27,7 @@ druid.host=localhost druid.service=realtime druid.port=8083 -druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.31"] +druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.32"] druid.zk.service.host=localhost diff --git a/docs/content/Tutorial:-A-First-Look-at-Druid.md b/docs/content/Tutorial:-A-First-Look-at-Druid.md index b276378fd5b..2561f6759a2 100644 --- a/docs/content/Tutorial:-A-First-Look-at-Druid.md +++ b/docs/content/Tutorial:-A-First-Look-at-Druid.md @@ -49,7 +49,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu ### Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.31-bin.tar.gz). Download this file to a directory of your choosing. +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.32-bin.tar.gz). Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -60,7 +60,7 @@ tar -zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.31 +cd druid-services-0.6.32 ``` You should see a bunch of files: diff --git a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md index 2ceec4f209a..f4ff840dec3 100644 --- a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md +++ b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md @@ -44,7 +44,7 @@ With real-world data, we recommend having a message bus such as [Apache Kafka](h #### Setting up Kafka -[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.31/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. +[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.32/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. Instructions for booting a Zookeeper and then Kafka cluster are available [here](http://kafka.apache.org/07/quickstart.html). diff --git a/docs/content/Tutorial:-The-Druid-Cluster.md b/docs/content/Tutorial:-The-Druid-Cluster.md index d30c65a8a8b..2912c8aaa5e 100644 --- a/docs/content/Tutorial:-The-Druid-Cluster.md +++ b/docs/content/Tutorial:-The-Druid-Cluster.md @@ -13,7 +13,7 @@ In this tutorial, we will set up other types of Druid nodes as well as and exter If you followed the first tutorial, you should already have Druid downloaded. If not, let's go back and do that first. -You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.31-bin.tar.gz) +You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.32-bin.tar.gz) and untar the contents within by issuing: @@ -149,7 +149,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.31"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.32"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b @@ -238,7 +238,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.31","io.druid.extensions:druid-kafka-seven:0.6.31"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.32","io.druid.extensions:druid-kafka-seven:0.6.32"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/docs/content/Tutorial:-Webstream.md b/docs/content/Tutorial:-Webstream.md index 007feaea850..ce372705958 100644 --- a/docs/content/Tutorial:-Webstream.md +++ b/docs/content/Tutorial:-Webstream.md @@ -37,7 +37,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.31-bin.tar.gz) +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.32-bin.tar.gz) Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -48,7 +48,7 @@ tar zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.31 +cd druid-services-0.6.32 ``` You should see a bunch of files: diff --git a/docs/content/Twitter-Tutorial.textile b/docs/content/Twitter-Tutorial.textile index cf2282b354c..932ea0c4b9c 100644 --- a/docs/content/Twitter-Tutorial.textile +++ b/docs/content/Twitter-Tutorial.textile @@ -9,7 +9,7 @@ There are two ways to setup Druid: download a tarball, or build it from source. h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.31-bin.tar.gz. +We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.32-bin.tar.gz. Download this bad boy to a directory of your choosing. You can extract the awesomeness within by issuing: diff --git a/examples/config/historical/runtime.properties b/examples/config/historical/runtime.properties index b4344b8e775..8efa9fd7063 100644 --- a/examples/config/historical/runtime.properties +++ b/examples/config/historical/runtime.properties @@ -4,7 +4,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.31"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.32"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b diff --git a/examples/config/realtime/runtime.properties b/examples/config/realtime/runtime.properties index 9303f37165a..569f76943f6 100644 --- a/examples/config/realtime/runtime.properties +++ b/examples/config/realtime/runtime.properties @@ -4,7 +4,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.31","io.druid.extensions:druid-kafka-seven:0.6.31","io.druid.extensions:druid-rabbitmq:0.6.31"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.32","io.druid.extensions:druid-kafka-seven:0.6.32","io.druid.extensions:druid-rabbitmq:0.6.32"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/services/src/main/java/io/druid/cli/CliBroker.java b/services/src/main/java/io/druid/cli/CliBroker.java index b5fc025af24..f1e57a6796a 100644 --- a/services/src/main/java/io/druid/cli/CliBroker.java +++ b/services/src/main/java/io/druid/cli/CliBroker.java @@ -53,7 +53,7 @@ import java.util.List; */ @Command( name = "broker", - description = "Runs a broker node, see http://druid.io/docs/0.6.31/Broker.html for a description" + description = "Runs a broker node, see http://druid.io/docs/0.6.32/Broker.html for a description" ) public class CliBroker extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliCoordinator.java b/services/src/main/java/io/druid/cli/CliCoordinator.java index f12d1f893e8..a24e3953acc 100644 --- a/services/src/main/java/io/druid/cli/CliCoordinator.java +++ b/services/src/main/java/io/druid/cli/CliCoordinator.java @@ -63,7 +63,7 @@ import java.util.List; */ @Command( name = "coordinator", - description = "Runs the Coordinator, see http://druid.io/docs/0.6.31/Coordinator.html for a description." + description = "Runs the Coordinator, see http://druid.io/docs/0.6.32/Coordinator.html for a description." ) public class CliCoordinator extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java index bb0214ca26f..ea5188d30b8 100644 --- a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java +++ b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java @@ -41,7 +41,7 @@ import java.util.List; */ @Command( name = "hadoop", - description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.31/Batch-ingestion.html for a description." + description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.32/Batch-ingestion.html for a description." ) public class CliHadoopIndexer implements Runnable { diff --git a/services/src/main/java/io/druid/cli/CliHistorical.java b/services/src/main/java/io/druid/cli/CliHistorical.java index 0adf699140e..606e62b7801 100644 --- a/services/src/main/java/io/druid/cli/CliHistorical.java +++ b/services/src/main/java/io/druid/cli/CliHistorical.java @@ -42,7 +42,7 @@ import java.util.List; */ @Command( name = "historical", - description = "Runs a Historical node, see http://druid.io/docs/0.6.31/Historical.html for a description" + description = "Runs a Historical node, see http://druid.io/docs/0.6.32/Historical.html for a description" ) public class CliHistorical extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index b4b2fa4071a..9f09a4e3ee3 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -95,7 +95,7 @@ import java.util.List; */ @Command( name = "overlord", - description = "Runs an Overlord node, see http://druid.io/docs/0.6.31/Indexing-Service.html for a description" + description = "Runs an Overlord node, see http://druid.io/docs/0.6.32/Indexing-Service.html for a description" ) public class CliOverlord extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtime.java b/services/src/main/java/io/druid/cli/CliRealtime.java index 89452ed5c23..6425786b648 100644 --- a/services/src/main/java/io/druid/cli/CliRealtime.java +++ b/services/src/main/java/io/druid/cli/CliRealtime.java @@ -30,7 +30,7 @@ import java.util.List; */ @Command( name = "realtime", - description = "Runs a realtime node, see http://druid.io/docs/0.6.31/Realtime.html for a description" + description = "Runs a realtime node, see http://druid.io/docs/0.6.32/Realtime.html for a description" ) public class CliRealtime extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtimeExample.java b/services/src/main/java/io/druid/cli/CliRealtimeExample.java index dc7725f575b..e761eb9f447 100644 --- a/services/src/main/java/io/druid/cli/CliRealtimeExample.java +++ b/services/src/main/java/io/druid/cli/CliRealtimeExample.java @@ -42,7 +42,7 @@ import java.util.concurrent.Executor; */ @Command( name = "realtime", - description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.31/Realtime.html for a description" + description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.32/Realtime.html for a description" ) public class CliRealtimeExample extends ServerRunnable { From 857d24a4ca484fb427e77004046d36ac463734d9 Mon Sep 17 00:00:00 2001 From: fjy Date: Thu, 12 Dec 2013 17:28:34 -0800 Subject: [PATCH 065/189] [maven-release-plugin] prepare release druid-0.6.32 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 66432acbfa3..0e491c9b568 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32-SNAPSHOT + 0.6.32 diff --git a/common/pom.xml b/common/pom.xml index efb65548359..9b6edcc8a18 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32-SNAPSHOT + 0.6.32 diff --git a/examples/pom.xml b/examples/pom.xml index 4473c3704cf..f8375d3bb8e 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32-SNAPSHOT + 0.6.32 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index e0769a73a7b..69edda19ef3 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32-SNAPSHOT + 0.6.32 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 20be3ac56e1..4e947802d24 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32-SNAPSHOT + 0.6.32 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 5c73a020c75..a3090240de1 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32-SNAPSHOT + 0.6.32 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index f26fbc2a42b..227207b24bd 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32-SNAPSHOT + 0.6.32 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 750f2bb9b4e..1390c8aedc3 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32-SNAPSHOT + 0.6.32 diff --git a/pom.xml b/pom.xml index 6f4b68f5538..7565b199fb1 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.32-SNAPSHOT + 0.6.32 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.32 diff --git a/processing/pom.xml b/processing/pom.xml index 1ffb41c5e2f..1ee070a0692 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32-SNAPSHOT + 0.6.32 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 14e0d426797..09ae61fdf1a 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.32-SNAPSHOT + 0.6.32 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index bcc304c2405..dd7420aa4ad 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32-SNAPSHOT + 0.6.32 diff --git a/server/pom.xml b/server/pom.xml index 41eb69b15a8..aa30ba84cea 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32-SNAPSHOT + 0.6.32 diff --git a/services/pom.xml b/services/pom.xml index 9ba368a0b17..203ee2f45da 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.32-SNAPSHOT + 0.6.32 From f920dd6fb235c7d2d72adfdbc8f0fb4081406cb8 Mon Sep 17 00:00:00 2001 From: fjy Date: Thu, 12 Dec 2013 17:28:38 -0800 Subject: [PATCH 066/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 0e491c9b568..c81d9cd4c6f 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32 + 0.6.33-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index 9b6edcc8a18..cc784b42931 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32 + 0.6.33-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index f8375d3bb8e..a0f41c8055b 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32 + 0.6.33-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 69edda19ef3..3fef0349068 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32 + 0.6.33-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 4e947802d24..a41d924a0c4 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32 + 0.6.33-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index a3090240de1..ccfe000917d 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32 + 0.6.33-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 227207b24bd..74a56b03534 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32 + 0.6.33-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 1390c8aedc3..831328ad581 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32 + 0.6.33-SNAPSHOT diff --git a/pom.xml b/pom.xml index 7565b199fb1..7bab6e2ef92 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.32 + 0.6.33-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.32 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index 1ee070a0692..c198adc0d94 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32 + 0.6.33-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 09ae61fdf1a..f1eedb43110 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.32 + 0.6.33-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index dd7420aa4ad..ea76ffd75f6 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32 + 0.6.33-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index aa30ba84cea..d607a0904f2 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.32 + 0.6.33-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index 203ee2f45da..371d72b5e41 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.32 + 0.6.33-SNAPSHOT From 25ebba2c3810fb86fe2017fab2fc0c65e32bd3fb Mon Sep 17 00:00:00 2001 From: fjy Date: Thu, 12 Dec 2013 18:51:25 -0800 Subject: [PATCH 067/189] fix default start delay of indexing service and prepare for next release --- build.sh | 2 +- docs/content/Booting-a-production-cluster.md | 2 +- docs/content/Examples.md | 4 ++-- docs/content/Indexing-Service.md | 1 + docs/content/Realtime.md | 2 +- docs/content/Tutorial:-A-First-Look-at-Druid.md | 4 ++-- docs/content/Tutorial:-Loading-Your-Data-Part-2.md | 2 +- docs/content/Tutorial:-The-Druid-Cluster.md | 6 +++--- docs/content/Tutorial:-Webstream.md | 4 ++-- docs/content/Twitter-Tutorial.textile | 2 +- examples/config/historical/runtime.properties | 2 +- examples/config/overlord/runtime.properties | 1 + examples/config/realtime/runtime.properties | 2 +- services/src/main/java/io/druid/cli/CliBroker.java | 2 +- services/src/main/java/io/druid/cli/CliCoordinator.java | 2 +- services/src/main/java/io/druid/cli/CliHadoopIndexer.java | 2 +- services/src/main/java/io/druid/cli/CliHistorical.java | 2 +- services/src/main/java/io/druid/cli/CliOverlord.java | 2 +- services/src/main/java/io/druid/cli/CliRealtime.java | 2 +- services/src/main/java/io/druid/cli/CliRealtimeExample.java | 2 +- 20 files changed, 25 insertions(+), 23 deletions(-) diff --git a/build.sh b/build.sh index f5f420383fb..71b98b275d7 100755 --- a/build.sh +++ b/build.sh @@ -30,4 +30,4 @@ echo "For examples, see: " echo " " ls -1 examples/*/*sh echo " " -echo "See also http://druid.io/docs/0.6.32" +echo "See also http://druid.io/docs/0.6.33" diff --git a/docs/content/Booting-a-production-cluster.md b/docs/content/Booting-a-production-cluster.md index 80c51be4120..be9a0b77bc7 100644 --- a/docs/content/Booting-a-production-cluster.md +++ b/docs/content/Booting-a-production-cluster.md @@ -3,7 +3,7 @@ layout: doc_page --- # Booting a Single Node Cluster # -[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.32-bin.tar.gz). +[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.33-bin.tar.gz). The [ec2 run script](https://github.com/metamx/druid/blob/master/examples/bin/run_ec2.sh), run_ec2.sh, is located at 'examples/bin' if you have checked out the code, or at the root of the project if you've downloaded a tarball. The scripts rely on the [Amazon EC2 API Tools](http://aws.amazon.com/developertools/351), and you will need to set three environment variables: diff --git a/docs/content/Examples.md b/docs/content/Examples.md index b372aac929a..1c6cf675b01 100644 --- a/docs/content/Examples.md +++ b/docs/content/Examples.md @@ -19,13 +19,13 @@ Clone Druid and build it: git clone https://github.com/metamx/druid.git druid cd druid git fetch --tags -git checkout druid-0.6.32 +git checkout druid-0.6.33 ./build.sh ``` ### Downloading the DSK (Druid Standalone Kit) -[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.32-bin.tar.gz) a stand-alone tarball and run it: +[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.33-bin.tar.gz) a stand-alone tarball and run it: ``` bash tar -xzf druid-services-0.X.X-bin.tar.gz diff --git a/docs/content/Indexing-Service.md b/docs/content/Indexing-Service.md index 0d1cc38621e..147b4d16467 100644 --- a/docs/content/Indexing-Service.md +++ b/docs/content/Indexing-Service.md @@ -56,6 +56,7 @@ With the following JVM configuration: -Ddruid.db.connector.password=diurd -Ddruid.selectors.indexing.serviceName=overlord +-Ddruid.indexer.queue.startDelay=PT0M -Ddruid.indexer.runner.javaOpts="-server -Xmx1g" -Ddruid.indexer.runner.startPort=8081 -Ddruid.indexer.fork.property.druid.computation.buffer.size=268435456 diff --git a/docs/content/Realtime.md b/docs/content/Realtime.md index 23d66bb78e9..adedc001fd0 100644 --- a/docs/content/Realtime.md +++ b/docs/content/Realtime.md @@ -27,7 +27,7 @@ druid.host=localhost druid.service=realtime druid.port=8083 -druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.32"] +druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.33"] druid.zk.service.host=localhost diff --git a/docs/content/Tutorial:-A-First-Look-at-Druid.md b/docs/content/Tutorial:-A-First-Look-at-Druid.md index 2561f6759a2..2de698d100d 100644 --- a/docs/content/Tutorial:-A-First-Look-at-Druid.md +++ b/docs/content/Tutorial:-A-First-Look-at-Druid.md @@ -49,7 +49,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu ### Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.32-bin.tar.gz). Download this file to a directory of your choosing. +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.33-bin.tar.gz). Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -60,7 +60,7 @@ tar -zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.32 +cd druid-services-0.6.33 ``` You should see a bunch of files: diff --git a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md index f4ff840dec3..f2046e7847a 100644 --- a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md +++ b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md @@ -44,7 +44,7 @@ With real-world data, we recommend having a message bus such as [Apache Kafka](h #### Setting up Kafka -[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.32/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. +[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.33/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. Instructions for booting a Zookeeper and then Kafka cluster are available [here](http://kafka.apache.org/07/quickstart.html). diff --git a/docs/content/Tutorial:-The-Druid-Cluster.md b/docs/content/Tutorial:-The-Druid-Cluster.md index 2912c8aaa5e..0c49d7a2315 100644 --- a/docs/content/Tutorial:-The-Druid-Cluster.md +++ b/docs/content/Tutorial:-The-Druid-Cluster.md @@ -13,7 +13,7 @@ In this tutorial, we will set up other types of Druid nodes as well as and exter If you followed the first tutorial, you should already have Druid downloaded. If not, let's go back and do that first. -You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.32-bin.tar.gz) +You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.33-bin.tar.gz) and untar the contents within by issuing: @@ -149,7 +149,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.32"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.33"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b @@ -238,7 +238,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.32","io.druid.extensions:druid-kafka-seven:0.6.32"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.33","io.druid.extensions:druid-kafka-seven:0.6.33"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/docs/content/Tutorial:-Webstream.md b/docs/content/Tutorial:-Webstream.md index ce372705958..02c622d3ef8 100644 --- a/docs/content/Tutorial:-Webstream.md +++ b/docs/content/Tutorial:-Webstream.md @@ -37,7 +37,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.32-bin.tar.gz) +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.33-bin.tar.gz) Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -48,7 +48,7 @@ tar zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.32 +cd druid-services-0.6.33 ``` You should see a bunch of files: diff --git a/docs/content/Twitter-Tutorial.textile b/docs/content/Twitter-Tutorial.textile index 932ea0c4b9c..c86423a8066 100644 --- a/docs/content/Twitter-Tutorial.textile +++ b/docs/content/Twitter-Tutorial.textile @@ -9,7 +9,7 @@ There are two ways to setup Druid: download a tarball, or build it from source. h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.32-bin.tar.gz. +We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.33-bin.tar.gz. Download this bad boy to a directory of your choosing. You can extract the awesomeness within by issuing: diff --git a/examples/config/historical/runtime.properties b/examples/config/historical/runtime.properties index 8efa9fd7063..4a1a78f984a 100644 --- a/examples/config/historical/runtime.properties +++ b/examples/config/historical/runtime.properties @@ -4,7 +4,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.32"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.33"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b diff --git a/examples/config/overlord/runtime.properties b/examples/config/overlord/runtime.properties index c9c4478d4c4..f452a7d06d6 100644 --- a/examples/config/overlord/runtime.properties +++ b/examples/config/overlord/runtime.properties @@ -9,6 +9,7 @@ druid.db.connector.user=druid druid.db.connector.password=diurd druid.selectors.indexing.serviceName=overlord +druid.indexer.queue.startDelay=PT0M druid.indexer.runner.javaOpts="-server -Xmx1g" druid.indexer.runner.startPort=8088 druid.indexer.fork.property.druid.computation.buffer.size=268435456 diff --git a/examples/config/realtime/runtime.properties b/examples/config/realtime/runtime.properties index 569f76943f6..fb79a8fc56a 100644 --- a/examples/config/realtime/runtime.properties +++ b/examples/config/realtime/runtime.properties @@ -4,7 +4,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.32","io.druid.extensions:druid-kafka-seven:0.6.32","io.druid.extensions:druid-rabbitmq:0.6.32"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.33","io.druid.extensions:druid-kafka-seven:0.6.33","io.druid.extensions:druid-rabbitmq:0.6.33"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/services/src/main/java/io/druid/cli/CliBroker.java b/services/src/main/java/io/druid/cli/CliBroker.java index f1e57a6796a..5e20ee4bf8a 100644 --- a/services/src/main/java/io/druid/cli/CliBroker.java +++ b/services/src/main/java/io/druid/cli/CliBroker.java @@ -53,7 +53,7 @@ import java.util.List; */ @Command( name = "broker", - description = "Runs a broker node, see http://druid.io/docs/0.6.32/Broker.html for a description" + description = "Runs a broker node, see http://druid.io/docs/0.6.33/Broker.html for a description" ) public class CliBroker extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliCoordinator.java b/services/src/main/java/io/druid/cli/CliCoordinator.java index a24e3953acc..dcf638ab391 100644 --- a/services/src/main/java/io/druid/cli/CliCoordinator.java +++ b/services/src/main/java/io/druid/cli/CliCoordinator.java @@ -63,7 +63,7 @@ import java.util.List; */ @Command( name = "coordinator", - description = "Runs the Coordinator, see http://druid.io/docs/0.6.32/Coordinator.html for a description." + description = "Runs the Coordinator, see http://druid.io/docs/0.6.33/Coordinator.html for a description." ) public class CliCoordinator extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java index ea5188d30b8..a6609936fac 100644 --- a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java +++ b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java @@ -41,7 +41,7 @@ import java.util.List; */ @Command( name = "hadoop", - description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.32/Batch-ingestion.html for a description." + description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.33/Batch-ingestion.html for a description." ) public class CliHadoopIndexer implements Runnable { diff --git a/services/src/main/java/io/druid/cli/CliHistorical.java b/services/src/main/java/io/druid/cli/CliHistorical.java index 606e62b7801..29d436ff7e4 100644 --- a/services/src/main/java/io/druid/cli/CliHistorical.java +++ b/services/src/main/java/io/druid/cli/CliHistorical.java @@ -42,7 +42,7 @@ import java.util.List; */ @Command( name = "historical", - description = "Runs a Historical node, see http://druid.io/docs/0.6.32/Historical.html for a description" + description = "Runs a Historical node, see http://druid.io/docs/0.6.33/Historical.html for a description" ) public class CliHistorical extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index 9f09a4e3ee3..9b869ce875e 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -95,7 +95,7 @@ import java.util.List; */ @Command( name = "overlord", - description = "Runs an Overlord node, see http://druid.io/docs/0.6.32/Indexing-Service.html for a description" + description = "Runs an Overlord node, see http://druid.io/docs/0.6.33/Indexing-Service.html for a description" ) public class CliOverlord extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtime.java b/services/src/main/java/io/druid/cli/CliRealtime.java index 6425786b648..aa27e625538 100644 --- a/services/src/main/java/io/druid/cli/CliRealtime.java +++ b/services/src/main/java/io/druid/cli/CliRealtime.java @@ -30,7 +30,7 @@ import java.util.List; */ @Command( name = "realtime", - description = "Runs a realtime node, see http://druid.io/docs/0.6.32/Realtime.html for a description" + description = "Runs a realtime node, see http://druid.io/docs/0.6.33/Realtime.html for a description" ) public class CliRealtime extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtimeExample.java b/services/src/main/java/io/druid/cli/CliRealtimeExample.java index e761eb9f447..6a51fdcc0d7 100644 --- a/services/src/main/java/io/druid/cli/CliRealtimeExample.java +++ b/services/src/main/java/io/druid/cli/CliRealtimeExample.java @@ -42,7 +42,7 @@ import java.util.concurrent.Executor; */ @Command( name = "realtime", - description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.32/Realtime.html for a description" + description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.33/Realtime.html for a description" ) public class CliRealtimeExample extends ServerRunnable { From 1d1baf5976322ae54f396ab3ec5784c8e28f5b0e Mon Sep 17 00:00:00 2001 From: fjy Date: Thu, 12 Dec 2013 18:53:14 -0800 Subject: [PATCH 068/189] [maven-release-plugin] prepare release druid-0.6.33 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index c81d9cd4c6f..820daa67bc7 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33-SNAPSHOT + 0.6.33 diff --git a/common/pom.xml b/common/pom.xml index cc784b42931..309df2077d4 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33-SNAPSHOT + 0.6.33 diff --git a/examples/pom.xml b/examples/pom.xml index a0f41c8055b..88fbc378d7f 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33-SNAPSHOT + 0.6.33 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 3fef0349068..e4573a0862d 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33-SNAPSHOT + 0.6.33 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index a41d924a0c4..019486bc7a6 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33-SNAPSHOT + 0.6.33 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index ccfe000917d..ce2fe34d330 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33-SNAPSHOT + 0.6.33 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 74a56b03534..84f333a0dcc 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33-SNAPSHOT + 0.6.33 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 831328ad581..62b0c2511ba 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33-SNAPSHOT + 0.6.33 diff --git a/pom.xml b/pom.xml index 7bab6e2ef92..d7a9e739c04 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.33-SNAPSHOT + 0.6.33 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.33 diff --git a/processing/pom.xml b/processing/pom.xml index c198adc0d94..305b7cb1f19 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33-SNAPSHOT + 0.6.33 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index f1eedb43110..eec02276add 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.33-SNAPSHOT + 0.6.33 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index ea76ffd75f6..1b4287d5cda 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33-SNAPSHOT + 0.6.33 diff --git a/server/pom.xml b/server/pom.xml index d607a0904f2..454654f2d00 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33-SNAPSHOT + 0.6.33 diff --git a/services/pom.xml b/services/pom.xml index 371d72b5e41..a52548a61cd 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.33-SNAPSHOT + 0.6.33 From aef7726c335f2003c5b502511825c3a108effc9c Mon Sep 17 00:00:00 2001 From: fjy Date: Thu, 12 Dec 2013 18:53:18 -0800 Subject: [PATCH 069/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 820daa67bc7..47756cfa602 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33 + 0.6.34-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index 309df2077d4..2e497083c06 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33 + 0.6.34-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index 88fbc378d7f..e221d145cf8 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33 + 0.6.34-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index e4573a0862d..1c646fa9f30 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33 + 0.6.34-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 019486bc7a6..c9cf5a0302f 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33 + 0.6.34-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index ce2fe34d330..ea3949c7b71 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33 + 0.6.34-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 84f333a0dcc..b41901f813c 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33 + 0.6.34-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 62b0c2511ba..984ed52c9b3 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33 + 0.6.34-SNAPSHOT diff --git a/pom.xml b/pom.xml index d7a9e739c04..56e856f4773 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.33 + 0.6.34-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.33 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index 305b7cb1f19..14ec5a3fc29 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33 + 0.6.34-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index eec02276add..febab9e8ffc 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.33 + 0.6.34-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 1b4287d5cda..4853bce92d7 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33 + 0.6.34-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index 454654f2d00..85849f4958f 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.33 + 0.6.34-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index a52548a61cd..768c1af3007 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.33 + 0.6.34-SNAPSHOT From 65facc935d7eafc326a1058e4893b3e905fcc3ea Mon Sep 17 00:00:00 2001 From: Himadri Singh Date: Fri, 13 Dec 2013 13:05:57 +0530 Subject: [PATCH 070/189] Do not load extensions on each call --- .../druid/initialization/Initialization.java | 14 +++++ .../java/io/druid/server/StatusResource.java | 54 ++++++++++++++++--- 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/io/druid/initialization/Initialization.java b/server/src/main/java/io/druid/initialization/Initialization.java index bb30718a33f..4051f70a7c8 100644 --- a/server/src/main/java/io/druid/initialization/Initialization.java +++ b/server/src/main/java/io/druid/initialization/Initialization.java @@ -101,6 +101,16 @@ public class Initialization "io.druid", "com.metamx.druid" ); + private static List loadedDruidModules = Lists.newArrayList(); + + /** + * @return List of {@link DruidModule}, once loaded. + */ + public static List getLoadedDruidModules() + { + return loadedDruidModules; + } + public synchronized static List getFromExtensions(ExtensionsConfig config, Class clazz) { @@ -131,6 +141,10 @@ public class Initialization } } + if (clazz.equals(DruidModule.class)){ + loadedDruidModules.addAll(retVal); + } + return retVal; } diff --git a/server/src/main/java/io/druid/server/StatusResource.java b/server/src/main/java/io/druid/server/StatusResource.java index d6e5371f0c3..9a281478524 100644 --- a/server/src/main/java/io/druid/server/StatusResource.java +++ b/server/src/main/java/io/druid/server/StatusResource.java @@ -19,12 +19,16 @@ package io.druid.server; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import io.druid.initialization.DruidModule; import io.druid.initialization.Initialization; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; +import java.util.ArrayList; +import java.util.List; /** */ @@ -42,20 +46,42 @@ public class StatusResource { return new Status( Initialization.class.getPackage().getImplementationVersion(), + getExtensionVersions(), new Memory(Runtime.getRuntime()) ); } + /** + * Load the unique extensions and return their implementation-versions + * + * @return map of extensions loaded with their respective implementation versions. + */ + private static List getExtensionVersions() + { + final List druidModules = Initialization.getLoadedDruidModules(); + + List moduleVersions = new ArrayList<>(); + for (DruidModule module : druidModules) { + String artifact = module.getClass().getPackage().getImplementationTitle(); + String version = module.getClass().getPackage().getImplementationVersion(); + + moduleVersions.add(new ModuleVersion(module.getClass().getCanonicalName(), artifact, version)); + } + return moduleVersions; + } + public static class Status { final String version; + final List modules; final Memory memory; public Status( - String version, Memory memory + String version, List modules, Memory memory ) { this.version = version; + this.modules = modules; this.memory = memory; } @@ -65,6 +91,12 @@ public class StatusResource return version; } + @JsonProperty + public List getModules() + { + return modules; + } + @JsonProperty public Memory getMemory() { @@ -75,21 +107,29 @@ public class StatusResource public String toString() { final String NL = System.getProperty("line.separator"); - return String.format("Druid version - %s", version) + NL; + StringBuilder output = new StringBuilder(); + output.append(String.format("Druid version - %s", version)).append(NL).append(NL); + + if (modules.size() > 0) { + output.append("Registered Druid Modules").append(NL); + } else { + output.append("No Druid Modules loaded !"); + } + + for (ModuleVersion moduleVersion : modules) { + output.append(moduleVersion).append(NL); + } + return output.toString(); } } + @JsonInclude(JsonInclude.Include.NON_NULL) public static class ModuleVersion { final String name; final String artifact; final String version; - public ModuleVersion(String name) - { - this(name, "", ""); - } - public ModuleVersion(String name, String artifact, String version) { this.name = name; From 6227963af9d9a7065438ab06ac0f43418ff82925 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 13 Dec 2013 00:35:33 -0800 Subject: [PATCH 071/189] TaskQueue: Copy task list before management loop. --- .../druid/indexing/common/task/NoopTask.java | 57 +++++++++++++++++-- .../io/druid/indexing/overlord/TaskQueue.java | 4 +- .../indexing/common/TestRealtimeTask.java | 9 +-- .../indexing/overlord/TaskLifecycleTest.java | 43 +++++++++----- 4 files changed, 86 insertions(+), 27 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/NoopTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/NoopTask.java index 001e1520eaa..d45f66377b7 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/NoopTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/NoopTask.java @@ -20,7 +20,9 @@ package io.druid.indexing.common.task; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.metamx.common.ISE; import com.metamx.common.logger.Logger; import io.druid.data.input.FirehoseFactory; import io.druid.indexing.common.TaskStatus; @@ -34,14 +36,34 @@ public class NoopTask extends AbstractTask { private static final Logger log = new Logger(NoopTask.class); private static int defaultRunTime = 2500; + private static int defaultIsReadyTime = 0; + private static IsReadyResult defaultIsReadyResult = IsReadyResult.YES; - private final int runTime; + enum IsReadyResult + { + YES, + NO, + EXCEPTION + } + + @JsonIgnore + private final long runTime; + + @JsonIgnore + private final long isReadyTime; + + @JsonIgnore + private final IsReadyResult isReadyResult; + + @JsonIgnore private final FirehoseFactory firehoseFactory; @JsonCreator public NoopTask( @JsonProperty("id") String id, - @JsonProperty("runTime") int runTime, + @JsonProperty("runTime") long runTime, + @JsonProperty("isReadyTime") long isReadyTime, + @JsonProperty("isReadyResult") String isReadyResult, @JsonProperty("firehose") FirehoseFactory firehoseFactory ) { @@ -51,6 +73,10 @@ public class NoopTask extends AbstractTask ); this.runTime = (runTime == 0) ? defaultRunTime : runTime; + this.isReadyTime = (isReadyTime == 0) ? defaultIsReadyTime : isReadyTime; + this.isReadyResult = (isReadyResult == null) + ? defaultIsReadyResult + : IsReadyResult.valueOf(isReadyResult.toUpperCase()); this.firehoseFactory = firehoseFactory; } @@ -60,12 +86,24 @@ public class NoopTask extends AbstractTask return "noop"; } - @JsonProperty("runTime") - public int getRunTime() + @JsonProperty + public long getRunTime() { return runTime; } + @JsonProperty + public long getIsReadyTime() + { + return isReadyTime; + } + + @JsonProperty + public IsReadyResult getIsReadyResult() + { + return isReadyResult; + } + @JsonProperty("firehose") public FirehoseFactory getFirehoseFactory() { @@ -75,7 +113,16 @@ public class NoopTask extends AbstractTask @Override public boolean isReady(TaskActionClient taskActionClient) throws Exception { - return true; + switch (isReadyResult) { + case YES: + return true; + case NO: + return false; + case EXCEPTION: + throw new ISE("Not ready. Never will be ready. Go away!"); + default: + throw new AssertionError("#notreached"); + } } @Override diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskQueue.java b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskQueue.java index 77e4b26e372..76ce14e8320 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskQueue.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskQueue.java @@ -24,6 +24,7 @@ import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -224,7 +225,8 @@ public class TaskQueue runnerTaskFutures.put(workItem.getTaskId(), workItem.getResult()); } // Attain futures for all active tasks (assuming they are ready to run). - for (final Task task : tasks) { + // Copy tasks list, as notifyStatus may modify it. + for (final Task task : ImmutableList.copyOf(tasks)) { if (!taskFutures.containsKey(task.getId())) { final ListenableFuture runnerTaskFuture; if (runnerTaskFutures.containsKey(task.getId())) { diff --git a/indexing-service/src/test/java/io/druid/indexing/common/TestRealtimeTask.java b/indexing-service/src/test/java/io/druid/indexing/common/TestRealtimeTask.java index cc69067d23c..178cae10513 100644 --- a/indexing-service/src/test/java/io/druid/indexing/common/TestRealtimeTask.java +++ b/indexing-service/src/test/java/io/druid/indexing/common/TestRealtimeTask.java @@ -32,7 +32,7 @@ import io.druid.timeline.partition.NoneShardSpec; /** */ @JsonTypeName("test_realtime") -public class TestRealtimeTask extends RealtimeIndexTask implements TestTask +public class TestRealtimeTask extends RealtimeIndexTask { private final TaskStatus status; @@ -64,13 +64,6 @@ public class TestRealtimeTask extends RealtimeIndexTask implements TestTask return "test_realtime"; } - @Override - @JsonProperty - public TaskStatus getStatus() - { - return status; - } - @Override public TaskStatus run(TaskToolbox toolbox) throws Exception { diff --git a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java index ac90c1e12f0..910b0c04e36 100644 --- a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java @@ -19,6 +19,7 @@ package io.druid.indexing.overlord; +import com.google.api.client.repackaged.com.google.common.base.Preconditions; import com.google.common.base.Optional; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; @@ -115,7 +116,10 @@ public class TaskLifecycleTest tmp = Files.createTempDir(); - final TaskQueueConfig tqc = new DefaultObjectMapper().readValue("{\"startDelay\":\"PT0S\"}", TaskQueueConfig.class); + final TaskQueueConfig tqc = new DefaultObjectMapper().readValue( + "{\"startDelay\":\"PT0S\", \"restartDelay\":\"PT1S\"}", + TaskQueueConfig.class + ); ts = new HeapMemoryTaskStorage(); tsqa = new TaskStorageQueryAdapter(ts); tl = new TaskLockbox(ts); @@ -400,28 +404,41 @@ public class TaskLifecycleTest Assert.assertEquals("segments nuked", 0, mdc.getNuked().size()); } - private TaskStatus runTask(Task task) + private TaskStatus runTask(final Task task) throws Exception { + final Task dummyTask = new DefaultObjectMapper().readValue( + "{\"type\":\"noop\", \"isReadyResult\":\"exception\"}\"", + Task.class + ); final long startTime = System.currentTimeMillis(); + Preconditions.checkArgument(!task.getId().equals(dummyTask.getId())); + + tq.add(dummyTask); tq.add(task); - TaskStatus status; + TaskStatus retVal = null; - try { - while ((status = tsqa.getStatus(task.getId()).get()).isRunnable()) { - if (System.currentTimeMillis() > startTime + 10 * 1000) { - throw new ISE("Where did the task go?!: %s", task.getId()); + for (final String taskId : ImmutableList.of(dummyTask.getId(), task.getId())) { + try { + TaskStatus status; + while ((status = tsqa.getStatus(taskId).get()).isRunnable()) { + if (System.currentTimeMillis() > startTime + 10 * 1000) { + throw new ISE("Where did the task go?!: %s", task.getId()); + } + + Thread.sleep(100); } - - Thread.sleep(100); + if (taskId.equals(task.getId())) { + retVal = status; + } + } + catch (Exception e) { + throw Throwables.propagate(e); } } - catch (Exception e) { - throw Throwables.propagate(e); - } - return status; + return retVal; } private static class MockIndexerDBCoordinator extends IndexerDBCoordinator From 863012c384bcd8b87981520ffe51fbe244d79959 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 13 Dec 2013 00:38:20 -0800 Subject: [PATCH 072/189] TaskQueue: Exception during isReady does not warrant an alert. --- .../src/main/java/io/druid/indexing/overlord/TaskQueue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskQueue.java b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskQueue.java index 76ce14e8320..693a504542c 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskQueue.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskQueue.java @@ -238,7 +238,7 @@ public class TaskQueue taskIsReady = task.isReady(taskActionClientFactory.create(task)); } catch (Exception e) { - log.makeAlert(e, "Exception thrown during isReady").addData("task", task.getId()).emit(); + log.warn(e, "Exception thrown during isReady for task: %s", task.getId()); notifyStatus(task, TaskStatus.failure(task.getId())); continue; } From 3b053a66ff97c1e29ec8b0233562d7bdf92db2ca Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 13 Dec 2013 07:47:18 -0800 Subject: [PATCH 073/189] TaskLifecycleTest: Add test for never-ready task --- .../druid/indexing/overlord/TaskLifecycleTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java index 910b0c04e36..5f80f37d132 100644 --- a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java @@ -293,6 +293,20 @@ public class TaskLifecycleTest Assert.assertEquals("num segments nuked", 0, mdc.getNuked().size()); } + @Test + public void testNeverReadyTask() throws Exception + { + final Task neverReadyTask = new DefaultObjectMapper().readValue( + "{\"type\":\"noop\", \"isReadyResult\":\"exception\"}\"", + Task.class + ); + final TaskStatus status = runTask(neverReadyTask); + + Assert.assertEquals("statusCode", TaskStatus.Status.FAILED, status.getStatusCode()); + Assert.assertEquals("num segments published", 0, mdc.getPublished().size()); + Assert.assertEquals("num segments nuked", 0, mdc.getNuked().size()); + } + @Test public void testSimple() throws Exception { From f36a5b677c5e1a4fa5d05b7b82d63586b9cad6be Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 13 Dec 2013 07:48:09 -0800 Subject: [PATCH 074/189] TaskLifecycleTest: Add test for noop task --- .../druid/indexing/overlord/TaskLifecycleTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java index 5f80f37d132..21c349a69c8 100644 --- a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java @@ -293,6 +293,20 @@ public class TaskLifecycleTest Assert.assertEquals("num segments nuked", 0, mdc.getNuked().size()); } + @Test + public void testNoopTask() throws Exception + { + final Task noopTask = new DefaultObjectMapper().readValue( + "{\"type\":\"noop\", \"runTime\":\"100\"}\"", + Task.class + ); + final TaskStatus status = runTask(noopTask); + + Assert.assertEquals("statusCode", TaskStatus.Status.SUCCESS, status.getStatusCode()); + Assert.assertEquals("num segments published", 0, mdc.getPublished().size()); + Assert.assertEquals("num segments nuked", 0, mdc.getNuked().size()); + } + @Test public void testNeverReadyTask() throws Exception { From 407f8caf94cc2c8d8720c815e1b9ed12165bddc2 Mon Sep 17 00:00:00 2001 From: fjy Date: Fri, 13 Dec 2013 09:04:32 -0800 Subject: [PATCH 075/189] remove server monitor from list of default monitors --- docs/content/Tutorial:-The-Druid-Cluster.md | 2 +- .../src/main/java/io/druid/cli/convert/ConvertProperties.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/Tutorial:-The-Druid-Cluster.md b/docs/content/Tutorial:-The-Druid-Cluster.md index 0c49d7a2315..0bc2d936716 100644 --- a/docs/content/Tutorial:-The-Druid-Cluster.md +++ b/docs/content/Tutorial:-The-Druid-Cluster.md @@ -253,5 +253,5 @@ druid.processing.buffer.sizeBytes=10000000 Next Steps ---------- -If you are intested in how data flows through the different Druid components, check out the [Druid data flow architecture](Design.html). Now that you have an understanding of what the Druid cluster looks like, why not load some of your own data? +If you are interested in how data flows through the different Druid components, check out the [Druid data flow architecture](Design.html). Now that you have an understanding of what the Druid cluster looks like, why not load some of your own data? Check out the next [tutorial](Tutorial%3A-Loading-Your-Data-Part-1.html) section for more info! diff --git a/services/src/main/java/io/druid/cli/convert/ConvertProperties.java b/services/src/main/java/io/druid/cli/convert/ConvertProperties.java index 3cc867700d0..fdbf2c9f2f0 100644 --- a/services/src/main/java/io/druid/cli/convert/ConvertProperties.java +++ b/services/src/main/java/io/druid/cli/convert/ConvertProperties.java @@ -178,7 +178,7 @@ public class ConvertProperties implements Runnable } updatedProps.setProperty( - "druid.monitoring.monitors", "[\"io.druid.server.metrics.ServerMonitor\", \"com.metamx.metrics.SysMonitor\"]" + "druid.monitoring.monitors", "[\"com.metamx.metrics.SysMonitor\"]" ); BufferedWriter out = null; From 6c993d87bf24827cb13bed26612d8ba8fd43ebaf Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 13 Dec 2013 11:36:36 -0800 Subject: [PATCH 076/189] Indexing service API and GUI improvements! - New APIs: waitingTasks, completeTasks, task payload - GUI for the above, and for task logs + status --- .../indexing/overlord/DbTaskStorage.java | 54 +++- .../indexing/overlord/ForkingTaskRunner.java | 2 +- .../overlord/HeapMemoryTaskStorage.java | 34 ++- .../indexing/overlord/TaskRunnerWorkItem.java | 6 + .../druid/indexing/overlord/TaskStorage.java | 7 + .../overlord/TaskStorageQueryAdapter.java | 20 ++ .../overlord/http/OverlordResource.java | 262 +++++++++++++----- .../resources/indexer_static/console.html | 8 + .../indexer_static/js/console-0.0.1.js | 31 ++- 9 files changed, 341 insertions(+), 83 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java b/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java index 7d3ad05512e..ff2d82d03cf 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java @@ -34,7 +34,6 @@ import com.metamx.common.RetryUtils; import com.metamx.common.lifecycle.LifecycleStart; import com.metamx.common.lifecycle.LifecycleStop; import com.metamx.emitter.EmittingLogger; -import com.mysql.jdbc.exceptions.MySQLTimeoutException; import com.mysql.jdbc.exceptions.MySQLTransientException; import io.druid.db.DbConnector; import io.druid.db.DbTablesConfig; @@ -43,6 +42,7 @@ import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.actions.TaskAction; import io.druid.indexing.common.task.Task; import org.joda.time.DateTime; +import org.joda.time.Period; import org.skife.jdbi.v2.Handle; import org.skife.jdbi.v2.IDBI; import org.skife.jdbi.v2.exceptions.CallbackFailedException; @@ -65,6 +65,7 @@ public class DbTaskStorage implements TaskStorage private final DbTablesConfig dbTables; private final IDBI dbi; + private static final long RECENCY_THRESHOLD = new Period("PT24H").toStandardDuration().getMillis(); private static final EmittingLogger log = new EmittingLogger(DbTaskStorage.class); @Inject @@ -271,6 +272,45 @@ public class DbTaskStorage implements TaskStorage ); } + @Override + public List getRecentlyFinishedTaskStatuses() + { + final DateTime recent = new DateTime().minus(RECENCY_THRESHOLD); + return retryingHandle( + new HandleCallback>() + { + @Override + public List withHandle(Handle handle) throws Exception + { + final List> dbTasks = + handle.createQuery( + String.format( + "SELECT id, status_payload FROM %s WHERE active = 0 AND created_date >= :recent ORDER BY created_date", + dbTables.getTasksTable() + ) + ).bind("recent", recent.toString()).list(); + + final ImmutableList.Builder statuses = ImmutableList.builder(); + for (final Map row : dbTasks) { + final String id = row.get("id").toString(); + + try { + final TaskStatus status = jsonMapper.readValue((byte[]) row.get("status_payload"), TaskStatus.class); + if (status.isComplete()) { + statuses.add(status); + } + } + catch (Exception e) { + log.makeAlert(e, "Failed to parse status payload").addData("task", id).emit(); + } + } + + return statuses.build(); + } + } + ); + } + @Override public void addLock(final String taskid, final TaskLock taskLock) { @@ -407,7 +447,8 @@ public class DbTaskStorage implements TaskStorage for (final Map dbTaskLog : dbTaskLogs) { try { retList.add(jsonMapper.readValue((byte[]) dbTaskLog.get("log_payload"), TaskAction.class)); - } catch (Exception e) { + } + catch (Exception e) { log.makeAlert(e, "Failed to deserialize TaskLog") .addData("task", taskid) .addData("logPayload", dbTaskLog) @@ -451,7 +492,8 @@ public class DbTaskStorage implements TaskStorage /** * Retry SQL operations */ - private T retryingHandle(final HandleCallback callback) { + private T retryingHandle(final HandleCallback callback) + { final Callable call = new Callable() { @Override @@ -471,9 +513,11 @@ public class DbTaskStorage implements TaskStorage final int maxTries = 10; try { return RetryUtils.retry(call, shouldRetry, maxTries); - } catch (RuntimeException e) { + } + catch (RuntimeException e) { throw Throwables.propagate(e); - } catch (Exception e) { + } + catch (Exception e) { throw new CallbackFailedException(e); } } diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/ForkingTaskRunner.java b/indexing-service/src/main/java/io/druid/indexing/overlord/ForkingTaskRunner.java index fce401c6641..1099bddbcc6 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/ForkingTaskRunner.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/ForkingTaskRunner.java @@ -391,7 +391,7 @@ public class ForkingTaskRunner implements TaskRunner, TaskLogStreamer if (offset > 0) { raf.seek(offset); } else if (offset < 0 && offset < rafLength) { - raf.seek(rafLength + offset); + raf.seek(Math.max(0, rafLength + offset)); } return Channels.newInputStream(raf.getChannel()); } diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/HeapMemoryTaskStorage.java b/indexing-service/src/main/java/io/druid/indexing/overlord/HeapMemoryTaskStorage.java index ef23972ebe4..79eb41f1fbc 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/HeapMemoryTaskStorage.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/HeapMemoryTaskStorage.java @@ -31,6 +31,8 @@ import io.druid.indexing.common.TaskLock; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.actions.TaskAction; import io.druid.indexing.common.task.Task; +import org.joda.time.DateTime; +import org.joda.time.Period; import java.util.List; import java.util.Map; @@ -47,6 +49,7 @@ public class HeapMemoryTaskStorage implements TaskStorage private final Multimap taskLocks = HashMultimap.create(); private final Multimap taskActions = ArrayListMultimap.create(); + private static final long RECENCY_THRESHOLD = new Period("PT24H").toStandardDuration().getMillis(); private static final Logger log = new Logger(HeapMemoryTaskStorage.class); @Override @@ -69,7 +72,7 @@ public class HeapMemoryTaskStorage implements TaskStorage } log.info("Inserting task %s with status: %s", task.getId(), status); - tasks.put(task.getId(), new TaskStuff(task, status)); + tasks.put(task.getId(), new TaskStuff(task, status, new DateTime())); } finally { giant.unlock(); } @@ -139,7 +142,25 @@ public class HeapMemoryTaskStorage implements TaskStorage listBuilder.add(taskStuff.getTask()); } } + return listBuilder.build(); + } finally { + giant.unlock(); + } + } + @Override + public List getRecentlyFinishedTaskStatuses() + { + giant.lock(); + + try { + final ImmutableList.Builder listBuilder = ImmutableList.builder(); + final long recent = System.currentTimeMillis() - RECENCY_THRESHOLD; + for(final TaskStuff taskStuff : tasks.values()) { + if(taskStuff.getStatus().isComplete() && taskStuff.getCreatedDate().getMillis() > recent) { + listBuilder.add(taskStuff.getStatus()); + } + } return listBuilder.build(); } finally { giant.unlock(); @@ -212,8 +233,9 @@ public class HeapMemoryTaskStorage implements TaskStorage { final Task task; final TaskStatus status; + final DateTime createdDate; - private TaskStuff(Task task, TaskStatus status) + private TaskStuff(Task task, TaskStatus status, DateTime createdDate) { Preconditions.checkNotNull(task); Preconditions.checkNotNull(status); @@ -221,6 +243,7 @@ public class HeapMemoryTaskStorage implements TaskStorage this.task = task; this.status = status; + this.createdDate = Preconditions.checkNotNull(createdDate, "createdDate"); } public Task getTask() @@ -233,9 +256,14 @@ public class HeapMemoryTaskStorage implements TaskStorage return status; } + public DateTime getCreatedDate() + { + return createdDate; + } + private TaskStuff withStatus(TaskStatus _status) { - return new TaskStuff(task, _status); + return new TaskStuff(task, _status, createdDate); } } } diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskRunnerWorkItem.java b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskRunnerWorkItem.java index a78faa24d03..2963c875257 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskRunnerWorkItem.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskRunnerWorkItem.java @@ -19,6 +19,8 @@ package io.druid.indexing.overlord; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ComparisonChain; import com.google.common.util.concurrent.ListenableFuture; import io.druid.indexing.common.TaskStatus; @@ -56,21 +58,25 @@ public class TaskRunnerWorkItem implements Comparable this.queueInsertionTime = queueInsertionTime; } + @JsonProperty public String getTaskId() { return taskId; } + @JsonIgnore public ListenableFuture getResult() { return result; } + @JsonProperty public DateTime getCreatedTime() { return createdTime; } + @JsonProperty public DateTime getQueueInsertionTime() { return queueInsertionTime; diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorage.java b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorage.java index 3a2145627df..191307fa949 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorage.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorage.java @@ -82,6 +82,13 @@ public interface TaskStorage */ public List getActiveTasks(); + /** + * Returns a list of recently finished task statuses as stored in the storage facility. No particular order + * is guaranteed. No particular standard of "recent" is guaranteed, and in fact, this method is permitted to + * simply return nothing. + */ + public List getRecentlyFinishedTaskStatuses(); + /** * Returns a list of locks for a particular task. */ diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorageQueryAdapter.java b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorageQueryAdapter.java index e9a2a8d5d7c..67ea11dcf33 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorageQueryAdapter.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorageQueryAdapter.java @@ -19,14 +19,19 @@ package io.druid.indexing.overlord; +import com.google.common.base.Function; import com.google.common.base.Optional; +import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.inject.Inject; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.actions.SegmentInsertAction; import io.druid.indexing.common.actions.TaskAction; +import io.druid.indexing.common.task.Task; import io.druid.timeline.DataSegment; +import javax.annotation.Nullable; +import java.util.List; import java.util.Set; /** @@ -42,6 +47,21 @@ public class TaskStorageQueryAdapter this.storage = storage; } + public List getActiveTasks() + { + return storage.getActiveTasks(); + } + + public List getRecentlyFinishedTaskStatuses() + { + return storage.getRecentlyFinishedTaskStatuses(); + } + + public Optional getTask(final String taskid) + { + return storage.getTask(taskid); + } + public Optional getStatus(final String taskid) { return storage.getStatus(taskid); diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java b/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java index d3d58bef03d..c3a8ab1224b 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java @@ -19,15 +19,20 @@ package io.druid.indexing.overlord.http; +import com.fasterxml.jackson.annotation.JsonValue; import com.google.common.base.Function; import com.google.common.base.Optional; -import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import com.google.common.io.InputSupplier; +import com.google.common.util.concurrent.SettableFuture; import com.google.inject.Inject; import com.metamx.common.logger.Logger; import io.druid.common.config.JacksonConfigManager; +import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.actions.TaskActionClient; import io.druid.indexing.common.actions.TaskActionHolder; import io.druid.indexing.common.task.Task; @@ -40,6 +45,7 @@ import io.druid.indexing.overlord.scaling.ResourceManagementScheduler; import io.druid.indexing.overlord.setup.WorkerSetupData; import io.druid.tasklogs.TaskLogStreamer; import io.druid.timeline.DataSegment; +import org.joda.time.DateTime; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; @@ -52,6 +58,8 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; import java.io.IOException; import java.io.InputStream; +import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; @@ -63,20 +71,6 @@ public class OverlordResource { private static final Logger log = new Logger(OverlordResource.class); - private static Function> simplifyTaskFn = - new Function>() - { - @Override - public Map apply(TaskRunnerWorkItem input) - { - return new ImmutableMap.Builder() - .put("id", input.getTaskId()) - .put("createdTime", input.getCreatedTime()) - .put("queueInsertionTime", input.getQueueInsertionTime()) - .build(); - } - }; - private final TaskMaster taskMaster; private final TaskStorageQueryAdapter taskStorageQueryAdapter; private final TaskLogStreamer taskLogStreamer; @@ -139,6 +133,14 @@ public class OverlordResource ); } + @GET + @Path("/task/{taskid}") + @Produces("application/json") + public Response getTaskPayload(@PathParam("taskid") String taskid) + { + return optionalTaskResponse(taskid, "payload", taskStorageQueryAdapter.getTask(taskid)); + } + @GET @Path("/task/{taskid}/status") @Produces("application/json") @@ -238,39 +240,64 @@ public class OverlordResource } @GET - @Path("/pendingTasks") + @Path("/waitingTasks") @Produces("application/json") - public Response getPendingTasks( - @QueryParam("full") String full - ) + public Response getWaitingTasks() { - if (full != null) { - return asLeaderWith( - taskMaster.getTaskRunner(), - new Function() - { - @Override - public Response apply(TaskRunner taskRunner) - { - return Response.ok(taskRunner.getPendingTasks()).build(); - } - } - ); - } - - return asLeaderWith( - taskMaster.getTaskRunner(), - new Function() + return workItemsResponse( + new Function>() { @Override - public Response apply(TaskRunner taskRunner) + public Collection apply(TaskRunner taskRunner) { - return Response.ok( - Collections2.transform( - taskRunner.getPendingTasks(), - simplifyTaskFn + // A bit roundabout, but works as a way of figuring out what tasks haven't been handed + // off to the runner yet: + final List activeTasks = taskStorageQueryAdapter.getActiveTasks(); + final Set runnersKnownTasks = Sets.newHashSet( + Iterables.transform( + taskRunner.getKnownTasks(), + new Function() + { + @Override + public String apply(final TaskRunnerWorkItem workItem) + { + return workItem.getTaskId(); + } + } ) - ).build(); + ); + final List waitingTasks = Lists.newArrayList(); + for (final Task task : activeTasks) { + if (!runnersKnownTasks.contains(task.getId())) { + waitingTasks.add( + // Would be nice to include the real created date, but the TaskStorage API doesn't yet allow it. + new TaskRunnerWorkItem( + task.getId(), + SettableFuture.create(), + new DateTime(0), + new DateTime(0) + ) + ); + } + } + return waitingTasks; + } + } + ); + } + + @GET + @Path("/pendingTasks") + @Produces("application/json") + public Response getPendingTasks() + { + return workItemsResponse( + new Function>() + { + @Override + public Collection apply(TaskRunner taskRunner) + { + return taskRunner.getPendingTasks(); } } ); @@ -279,42 +306,45 @@ public class OverlordResource @GET @Path("/runningTasks") @Produces("application/json") - public Response getRunningTasks( - @QueryParam("full") String full - ) + public Response getRunningTasks() { - if (full != null) { - return asLeaderWith( - taskMaster.getTaskRunner(), - new Function() - { - @Override - public Response apply(TaskRunner taskRunner) - { - return Response.ok(taskRunner.getRunningTasks()).build(); - } - } - ); - } - - return asLeaderWith( - taskMaster.getTaskRunner(), - new Function() + return workItemsResponse( + new Function>() { @Override - public Response apply(TaskRunner taskRunner) + public Collection apply(TaskRunner taskRunner) { - return Response.ok( - Collections2.transform( - taskRunner.getRunningTasks(), - simplifyTaskFn - ) - ).build(); + return taskRunner.getRunningTasks(); } } ); } + @GET + @Path("/completeTasks") + @Produces("application/json") + public Response getCompleteTasks() + { + final List completeTasks = Lists.transform( + taskStorageQueryAdapter.getRecentlyFinishedTaskStatuses(), + new Function() + { + @Override + public TaskResponseObject apply(TaskStatus taskStatus) + { + // Would be nice to include the real created date, but the TaskStorage API doesn't yet allow it. + return new TaskResponseObject( + taskStatus.getId(), + new DateTime(0), + new DateTime(0), + Optional.of(taskStatus) + ); + } + } + ); + return Response.ok(completeTasks).build(); + } + @GET @Path("/workers") @Produces("application/json") @@ -373,7 +403,39 @@ public class OverlordResource } } - public Response optionalTaskResponse(String taskid, String objectType, Optional x) + private Response workItemsResponse(final Function> fn) + { + return asLeaderWith( + taskMaster.getTaskRunner(), + new Function() + { + @Override + public Response apply(TaskRunner taskRunner) + { + return Response.ok( + Lists.transform( + Lists.newArrayList(fn.apply(taskRunner)), + new Function() + { + @Override + public TaskResponseObject apply(TaskRunnerWorkItem workItem) + { + return new TaskResponseObject( + workItem.getTaskId(), + workItem.getCreatedTime(), + workItem.getQueueInsertionTime(), + Optional.absent() + ); + } + } + ) + ).build(); + } + } + ); + } + + private Response optionalTaskResponse(String taskid, String objectType, Optional x) { final Map results = Maps.newHashMap(); results.put("task", taskid); @@ -385,7 +447,7 @@ public class OverlordResource } } - public Response asLeaderWith(Optional x, Function f) + private Response asLeaderWith(Optional x, Function f) { if (x.isPresent()) { return f.apply(x.get()); @@ -394,4 +456,62 @@ public class OverlordResource return Response.status(Response.Status.SERVICE_UNAVAILABLE).build(); } } + + private static class TaskResponseObject + { + private final String id; + private final DateTime createdTime; + private final DateTime queueInsertionTime; + private final Optional status; + + private TaskResponseObject( + String id, + DateTime createdTime, + DateTime queueInsertionTime, + Optional status + ) + { + this.id = id; + this.createdTime = createdTime; + this.queueInsertionTime = queueInsertionTime; + this.status = status; + } + + public String getId() + { + return id; + } + + public DateTime getCreatedTime() + { + return createdTime; + } + + public DateTime getQueueInsertionTime() + { + return queueInsertionTime; + } + + public Optional getStatus() + { + return status; + } + + @JsonValue + public Map toJson() + { + final Map data = Maps.newLinkedHashMap(); + data.put("id", id); + if (createdTime.getMillis() > 0) { + data.put("createdTime", createdTime); + } + if (queueInsertionTime.getMillis() > 0) { + data.put("queueInsertionTime", queueInsertionTime); + } + if (status.isPresent()) { + data.put("statusCode", status.get().getStatusCode().toString()); + } + return data; + } + } } diff --git a/indexing-service/src/main/resources/indexer_static/console.html b/indexing-service/src/main/resources/indexer_static/console.html index 6223eee6a1f..e782ff529ab 100644 --- a/indexing-service/src/main/resources/indexer_static/console.html +++ b/indexing-service/src/main/resources/indexer_static/console.html @@ -47,6 +47,14 @@

Loading Pending Tasks... this may take a few minutes
+

Waiting Tasks

+
Loading Waiting Tasks... this may take a few minutes
+
+ +

Complete Tasks

+
Loading Complete Tasks... this may take a few minutes
+
+

Workers

Loading Workers... this may take a few minutes
diff --git a/indexing-service/src/main/resources/indexer_static/js/console-0.0.1.js b/indexing-service/src/main/resources/indexer_static/js/console-0.0.1.js index e3ce86c85c9..adaa1fba83f 100644 --- a/indexing-service/src/main/resources/indexer_static/js/console-0.0.1.js +++ b/indexing-service/src/main/resources/indexer_static/js/console-0.0.1.js @@ -3,14 +3,39 @@ var oTable = []; $(document).ready(function() { + var augment = function(data) { + for (i = 0 ; i < data.length ; i++) { + var taskId = encodeURIComponent(data[i].id) + data[i].more = + '
payload' + + 'status' + + 'log (all)' + + 'log (last 8kb)' + } + } + $.get('/druid/indexer/v1/runningTasks', function(data) { $('.running_loading').hide(); - buildTable(data, $('#runningTable'), ["segments"]); + augment(data); + buildTable(data, $('#runningTable')); }); $.get('/druid/indexer/v1/pendingTasks', function(data) { $('.pending_loading').hide(); - buildTable(data, $('#pendingTable'), ["segments"]); + augment(data); + buildTable(data, $('#pendingTable')); + }); + + $.get('/druid/indexer/v1/waitingTasks', function(data) { + $('.waiting_loading').hide(); + augment(data); + buildTable(data, $('#waitingTable')); + }); + + $.get('/druid/indexer/v1/completeTasks', function(data) { + $('.complete_loading').hide(); + augment(data); + buildTable(data, $('#completeTable')); }); $.get('/druid/indexer/v1/workers', function(data) { @@ -22,4 +47,4 @@ $(document).ready(function() { $('.events_loading').hide(); buildTable(data, $('#eventTable')); }); -}); \ No newline at end of file +}); From e63c69dd57c33fe0fe169802e419b7b0cb5cfc8b Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 13 Dec 2013 12:27:45 -0800 Subject: [PATCH 077/189] TaskStorage: Return recently complete tasks in reverse chronological order --- .../indexing/overlord/DbTaskStorage.java | 2 +- .../overlord/HeapMemoryTaskStorage.java | 19 +++++++++++++++---- .../druid/indexing/overlord/TaskStorage.java | 4 ++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java b/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java index ff2d82d03cf..e39458ce0f1 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java @@ -285,7 +285,7 @@ public class DbTaskStorage implements TaskStorage final List> dbTasks = handle.createQuery( String.format( - "SELECT id, status_payload FROM %s WHERE active = 0 AND created_date >= :recent ORDER BY created_date", + "SELECT id, status_payload FROM %s WHERE active = 0 AND created_date >= :recent ORDER BY created_date DESC", dbTables.getTasksTable() ) ).bind("recent", recent.toString()).list(); diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/HeapMemoryTaskStorage.java b/indexing-service/src/main/java/io/druid/indexing/overlord/HeapMemoryTaskStorage.java index 79eb41f1fbc..4f2b72a0e63 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/HeapMemoryTaskStorage.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/HeapMemoryTaskStorage.java @@ -19,6 +19,7 @@ package io.druid.indexing.overlord; +import com.google.api.client.util.Lists; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.collect.ArrayListMultimap; @@ -26,6 +27,7 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; +import com.google.common.collect.Ordering; import com.metamx.common.logger.Logger; import io.druid.indexing.common.TaskLock; import io.druid.indexing.common.TaskStatus; @@ -34,6 +36,7 @@ import io.druid.indexing.common.task.Task; import org.joda.time.DateTime; import org.joda.time.Period; +import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; @@ -154,14 +157,22 @@ public class HeapMemoryTaskStorage implements TaskStorage giant.lock(); try { - final ImmutableList.Builder listBuilder = ImmutableList.builder(); + final List returns = Lists.newArrayList(); final long recent = System.currentTimeMillis() - RECENCY_THRESHOLD; - for(final TaskStuff taskStuff : tasks.values()) { + final Ordering createdDateDesc = new Ordering() + { + @Override + public int compare(TaskStuff a, TaskStuff b) + { + return a.getCreatedDate().compareTo(b.getCreatedDate()); + } + }.reverse(); + for(final TaskStuff taskStuff : createdDateDesc.sortedCopy(tasks.values())) { if(taskStuff.getStatus().isComplete() && taskStuff.getCreatedDate().getMillis() > recent) { - listBuilder.add(taskStuff.getStatus()); + returns.add(taskStuff.getStatus()); } } - return listBuilder.build(); + return returns; } finally { giant.unlock(); } diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorage.java b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorage.java index 191307fa949..fb289459256 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorage.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskStorage.java @@ -84,8 +84,8 @@ public interface TaskStorage /** * Returns a list of recently finished task statuses as stored in the storage facility. No particular order - * is guaranteed. No particular standard of "recent" is guaranteed, and in fact, this method is permitted to - * simply return nothing. + * is guaranteed, but implementations are encouraged to return tasks in descending order of creation. No particular + * standard of "recent" is guaranteed, and in fact, this method is permitted to simply return nothing. */ public List getRecentlyFinishedTaskStatuses(); From 52cdb20f106cbe1fd3064ba09e3f4203bd827268 Mon Sep 17 00:00:00 2001 From: fjy Date: Fri, 13 Dec 2013 15:01:07 -0800 Subject: [PATCH 078/189] add better messaging and error handling --- .../Tutorial:-Loading-Your-Data-Part-1.md | 15 +++++---- .../overlord/http/OverlordResource.java | 3 ++ .../resources/indexer_static/console.html | 4 +-- .../firehose/LocalFirehoseFactory.java | 32 +++++++++++-------- 4 files changed, 33 insertions(+), 21 deletions(-) diff --git a/docs/content/Tutorial:-Loading-Your-Data-Part-1.md b/docs/content/Tutorial:-Loading-Your-Data-Part-1.md index 2c56c81a839..40cd012bccd 100644 --- a/docs/content/Tutorial:-Loading-Your-Data-Part-1.md +++ b/docs/content/Tutorial:-Loading-Your-Data-Part-1.md @@ -94,6 +94,7 @@ druid.db.connector.user=druid druid.db.connector.password=diurd druid.selectors.indexing.serviceName=overlord +druid.indexer.queue.startDelay=PT0M druid.indexer.runner.javaOpts="-server -Xmx1g" druid.indexer.runner.startPort=8088 druid.indexer.fork.property.druid.computation.buffer.size=268435456 @@ -246,17 +247,19 @@ Issuing a [TimeBoundaryQuery](TimeBoundaryQuery.html) should yield: } ] ``` -Problems? ---------- +Console +-------- -If you decide to reuse the local firehose to ingest your own data and if you run into problems, you can read the individual task logs at: +The indexing service overlord has a console located as: ```bash -/log/.log - +localhost:8087/console.html ``` -One thing to note is that the log file will only exist once the task completes with either SUCCESS or FAILURE. +On this console, you can look at statuses and logs of recently submitted and completed tasks. + +If you decide to reuse the local firehose to ingest your own data and if you run into problems, you can use the console to read the individual task logs. + Task logs can be stored locally or uploaded to [Deep Storage](Deep-Storage.html). More information about how to configure this is [here](Configuration.html). Most common data ingestion problems are around timestamp formats and other malformed data issues. diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java b/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java index c3a8ab1224b..a4ffeafb78c 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java @@ -368,6 +368,9 @@ public class OverlordResource @Produces("application/json") public Response getScalingState() { + if (!taskMaster.getResourceManagementScheduler().isPresent()) { + return Response.ok().build(); + } return asLeaderWith( taskMaster.getResourceManagementScheduler(), new Function() diff --git a/indexing-service/src/main/resources/indexer_static/console.html b/indexing-service/src/main/resources/indexer_static/console.html index e782ff529ab..1a55daf7e95 100644 --- a/indexing-service/src/main/resources/indexer_static/console.html +++ b/indexing-service/src/main/resources/indexer_static/console.html @@ -59,8 +59,8 @@
Loading Workers... this may take a few minutes
-

Event Log

-
Loading Event Log... this may take a few minutes
+

Autoscaling Activity

+
Loading Autoscaling Activities... this may take a few minutes
diff --git a/server/src/main/java/io/druid/segment/realtime/firehose/LocalFirehoseFactory.java b/server/src/main/java/io/druid/segment/realtime/firehose/LocalFirehoseFactory.java index f78bc0ac390..df96aa45f5e 100644 --- a/server/src/main/java/io/druid/segment/realtime/firehose/LocalFirehoseFactory.java +++ b/server/src/main/java/io/druid/segment/realtime/firehose/LocalFirehoseFactory.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.api.client.repackaged.com.google.common.base.Throwables; import com.google.common.collect.Lists; +import com.metamx.common.ISE; import io.druid.data.input.Firehose; import io.druid.data.input.FirehoseFactory; import io.druid.data.input.impl.FileIteratingFirehose; @@ -78,21 +79,26 @@ public class LocalFirehoseFactory implements FirehoseFactory @Override public Firehose connect() throws IOException { - final LinkedList files = Lists.newLinkedList( - Arrays.asList( - baseDir.listFiles( - new FilenameFilter() - { - @Override - public boolean accept(File file, String name) - { - return name.contains(filter); - } - } - ) - ) + File[] foundFiles = baseDir.listFiles( + new FilenameFilter() + { + @Override + public boolean accept(File file, String name) + { + return name.contains(filter); + } + } ); + if (foundFiles == null || foundFiles.length == 0) { + throw new ISE("Found no files to ingest! Check your schema."); + } + + final LinkedList files = Lists.newLinkedList( + Arrays.asList(foundFiles) + ); + + return new FileIteratingFirehose( new Iterator() { From 4a8140be811b6986cd357a05a9d7102966214edd Mon Sep 17 00:00:00 2001 From: fjy Date: Fri, 13 Dec 2013 15:04:25 -0800 Subject: [PATCH 079/189] better messaging to console again --- .../src/main/resources/indexer_static/console.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indexing-service/src/main/resources/indexer_static/console.html b/indexing-service/src/main/resources/indexer_static/console.html index 1a55daf7e95..f51383c72c0 100644 --- a/indexing-service/src/main/resources/indexer_static/console.html +++ b/indexing-service/src/main/resources/indexer_static/console.html @@ -43,11 +43,11 @@
Loading Running Tasks... this may take a few minutes
-

Pending Tasks

+

Pending Tasks - Tasks waiting to be assigned to a worker

Loading Pending Tasks... this may take a few minutes
-

Waiting Tasks

+

Waiting Tasks - Tasks waiting on locks

Loading Waiting Tasks... this may take a few minutes
@@ -55,7 +55,7 @@
Loading Complete Tasks... this may take a few minutes
-

Workers

+

Remote Workers

Loading Workers... this may take a few minutes
From 600dc7546f8ef7d625b208d570f27eaee1219824 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 13 Dec 2013 16:02:54 -0800 Subject: [PATCH 080/189] Configurability of recency threshold --- .../common/config/TaskStorageConfig.java | 27 +++++++++++++++++++ .../indexing/overlord/DbTaskStorage.java | 14 +++++++--- .../overlord/HeapMemoryTaskStorage.java | 15 ++++++++--- .../overlord/http/OverlordResource.java | 17 ++++-------- .../indexing/overlord/TaskLifecycleTest.java | 9 ++++++- .../main/java/io/druid/cli/CliOverlord.java | 3 +++ 6 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 indexing-service/src/main/java/io/druid/indexing/common/config/TaskStorageConfig.java diff --git a/indexing-service/src/main/java/io/druid/indexing/common/config/TaskStorageConfig.java b/indexing-service/src/main/java/io/druid/indexing/common/config/TaskStorageConfig.java new file mode 100644 index 00000000000..fbbea09a8de --- /dev/null +++ b/indexing-service/src/main/java/io/druid/indexing/common/config/TaskStorageConfig.java @@ -0,0 +1,27 @@ +package io.druid.indexing.common.config; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.joda.time.Duration; +import org.joda.time.Period; + +public class TaskStorageConfig +{ + @JsonProperty + private final Duration recentlyFinishedThreshold; + + @JsonCreator + public TaskStorageConfig( + @JsonProperty("recentlyFinishedThreshold") final Period recentlyFinishedThreshold + ) + { + this.recentlyFinishedThreshold = recentlyFinishedThreshold == null + ? new Period("PT24H").toStandardDuration() + : recentlyFinishedThreshold.toStandardDuration(); + } + + public Duration getRecentlyFinishedThreshold() + { + return recentlyFinishedThreshold; + } +} diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java b/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java index e39458ce0f1..5dc6e1c6fff 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java @@ -40,6 +40,7 @@ import io.druid.db.DbTablesConfig; import io.druid.indexing.common.TaskLock; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.actions.TaskAction; +import io.druid.indexing.common.config.TaskStorageConfig; import io.druid.indexing.common.task.Task; import org.joda.time.DateTime; import org.joda.time.Period; @@ -64,17 +65,24 @@ public class DbTaskStorage implements TaskStorage private final DbConnector dbConnector; private final DbTablesConfig dbTables; private final IDBI dbi; + private final TaskStorageConfig config; - private static final long RECENCY_THRESHOLD = new Period("PT24H").toStandardDuration().getMillis(); private static final EmittingLogger log = new EmittingLogger(DbTaskStorage.class); @Inject - public DbTaskStorage(ObjectMapper jsonMapper, DbConnector dbConnector, DbTablesConfig dbTables, IDBI dbi) + public DbTaskStorage( + final ObjectMapper jsonMapper, + final DbConnector dbConnector, + final DbTablesConfig dbTables, + final IDBI dbi, + final TaskStorageConfig config + ) { this.jsonMapper = jsonMapper; this.dbConnector = dbConnector; this.dbTables = dbTables; this.dbi = dbi; + this.config = config; } @LifecycleStart @@ -275,7 +283,7 @@ public class DbTaskStorage implements TaskStorage @Override public List getRecentlyFinishedTaskStatuses() { - final DateTime recent = new DateTime().minus(RECENCY_THRESHOLD); + final DateTime recent = new DateTime().minus(config.getRecentlyFinishedThreshold()); return retryingHandle( new HandleCallback>() { diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/HeapMemoryTaskStorage.java b/indexing-service/src/main/java/io/druid/indexing/overlord/HeapMemoryTaskStorage.java index 4f2b72a0e63..ef942e5c12f 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/HeapMemoryTaskStorage.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/HeapMemoryTaskStorage.java @@ -28,15 +28,15 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Ordering; +import com.google.inject.Inject; import com.metamx.common.logger.Logger; import io.druid.indexing.common.TaskLock; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.actions.TaskAction; +import io.druid.indexing.common.config.TaskStorageConfig; import io.druid.indexing.common.task.Task; import org.joda.time.DateTime; -import org.joda.time.Period; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; @@ -47,14 +47,21 @@ import java.util.concurrent.locks.ReentrantLock; */ public class HeapMemoryTaskStorage implements TaskStorage { + private final TaskStorageConfig config; + private final ReentrantLock giant = new ReentrantLock(); private final Map tasks = Maps.newHashMap(); private final Multimap taskLocks = HashMultimap.create(); private final Multimap taskActions = ArrayListMultimap.create(); - private static final long RECENCY_THRESHOLD = new Period("PT24H").toStandardDuration().getMillis(); private static final Logger log = new Logger(HeapMemoryTaskStorage.class); + @Inject + public HeapMemoryTaskStorage(TaskStorageConfig config) + { + this.config = config; + } + @Override public void insert(Task task, TaskStatus status) { @@ -158,7 +165,7 @@ public class HeapMemoryTaskStorage implements TaskStorage try { final List returns = Lists.newArrayList(); - final long recent = System.currentTimeMillis() - RECENCY_THRESHOLD; + final long recent = System.currentTimeMillis() - config.getRecentlyFinishedThreshold().getMillis(); final Ordering createdDateDesc = new Ordering() { @Override diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java b/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java index a4ffeafb78c..f161cb3c278 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/http/OverlordResource.java @@ -368,20 +368,13 @@ public class OverlordResource @Produces("application/json") public Response getScalingState() { - if (!taskMaster.getResourceManagementScheduler().isPresent()) { + // Don't use asLeaderWith, since we want to return 200 instead of 503 when missing an autoscaler. + final Optional rms = taskMaster.getResourceManagementScheduler(); + if (rms.isPresent()) { + return Response.ok(rms.get().getStats()).build(); + } else { return Response.ok().build(); } - return asLeaderWith( - taskMaster.getResourceManagementScheduler(), - new Function() - { - @Override - public Response apply(ResourceManagementScheduler resourceManagementScheduler) - { - return Response.ok(resourceManagementScheduler.getStats()).build(); - } - } - ); } @GET diff --git a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java index 21c349a69c8..51f20e830a0 100644 --- a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java @@ -54,6 +54,7 @@ import io.druid.indexing.common.actions.SegmentInsertAction; import io.druid.indexing.common.actions.TaskActionClientFactory; import io.druid.indexing.common.actions.TaskActionToolbox; import io.druid.indexing.common.config.TaskConfig; +import io.druid.indexing.common.config.TaskStorageConfig; import io.druid.indexing.common.task.AbstractFixedIntervalTask; import io.druid.indexing.common.task.IndexTask; import io.druid.indexing.common.task.KillTask; @@ -75,7 +76,9 @@ import io.druid.timeline.DataSegment; import org.apache.commons.io.FileUtils; import org.easymock.EasyMock; import org.joda.time.DateTime; +import org.joda.time.Duration; import org.joda.time.Interval; +import org.joda.time.Period; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -120,7 +123,11 @@ public class TaskLifecycleTest "{\"startDelay\":\"PT0S\", \"restartDelay\":\"PT1S\"}", TaskQueueConfig.class ); - ts = new HeapMemoryTaskStorage(); + ts = new HeapMemoryTaskStorage( + new TaskStorageConfig(new Period("PT24H")) + { + } + ); tsqa = new TaskStorageQueryAdapter(ts); tl = new TaskLockbox(ts); mdc = newMockMDC(); diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index 9b869ce875e..166c6452eb9 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -45,6 +45,7 @@ import io.druid.indexing.common.actions.LocalTaskActionClientFactory; import io.druid.indexing.common.actions.TaskActionClientFactory; import io.druid.indexing.common.actions.TaskActionToolbox; import io.druid.indexing.common.config.TaskConfig; +import io.druid.indexing.common.config.TaskStorageConfig; import io.druid.indexing.common.index.ChatHandlerProvider; import io.druid.indexing.common.tasklogs.SwitchingTaskLogStreamer; import io.druid.indexing.common.tasklogs.TaskRunnerTaskLogStreamer; @@ -154,6 +155,8 @@ public class CliOverlord extends ServerRunnable private void configureTaskStorage(Binder binder) { + JsonConfigProvider.bind(binder, "druid.indexer.storage", TaskStorageConfig.class); + PolyBind.createChoice( binder, "druid.indexer.storage.type", Key.get(TaskStorage.class), Key.get(HeapMemoryTaskStorage.class) ); From 9a5fd6b0e9c04c198e30af747418c33e0292520a Mon Sep 17 00:00:00 2001 From: fjy Date: Fri, 13 Dec 2013 16:18:34 -0800 Subject: [PATCH 081/189] prepare for next release --- build.sh | 2 +- docs/content/Booting-a-production-cluster.md | 2 +- docs/content/Examples.md | 4 ++-- docs/content/Realtime.md | 2 +- docs/content/Tutorial:-A-First-Look-at-Druid.md | 4 ++-- docs/content/Tutorial:-Loading-Your-Data-Part-2.md | 2 +- docs/content/Tutorial:-The-Druid-Cluster.md | 6 +++--- docs/content/Tutorial:-Webstream.md | 4 ++-- docs/content/Twitter-Tutorial.textile | 2 +- examples/config/historical/runtime.properties | 2 +- examples/config/realtime/runtime.properties | 2 +- services/src/main/java/io/druid/cli/CliBroker.java | 2 +- services/src/main/java/io/druid/cli/CliCoordinator.java | 2 +- services/src/main/java/io/druid/cli/CliHadoopIndexer.java | 2 +- services/src/main/java/io/druid/cli/CliHistorical.java | 2 +- services/src/main/java/io/druid/cli/CliOverlord.java | 2 +- services/src/main/java/io/druid/cli/CliRealtime.java | 2 +- services/src/main/java/io/druid/cli/CliRealtimeExample.java | 2 +- 18 files changed, 23 insertions(+), 23 deletions(-) diff --git a/build.sh b/build.sh index 71b98b275d7..a786c523be9 100755 --- a/build.sh +++ b/build.sh @@ -30,4 +30,4 @@ echo "For examples, see: " echo " " ls -1 examples/*/*sh echo " " -echo "See also http://druid.io/docs/0.6.33" +echo "See also http://druid.io/docs/0.6.34" diff --git a/docs/content/Booting-a-production-cluster.md b/docs/content/Booting-a-production-cluster.md index be9a0b77bc7..b93c428eb69 100644 --- a/docs/content/Booting-a-production-cluster.md +++ b/docs/content/Booting-a-production-cluster.md @@ -3,7 +3,7 @@ layout: doc_page --- # Booting a Single Node Cluster # -[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.33-bin.tar.gz). +[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.34-bin.tar.gz). The [ec2 run script](https://github.com/metamx/druid/blob/master/examples/bin/run_ec2.sh), run_ec2.sh, is located at 'examples/bin' if you have checked out the code, or at the root of the project if you've downloaded a tarball. The scripts rely on the [Amazon EC2 API Tools](http://aws.amazon.com/developertools/351), and you will need to set three environment variables: diff --git a/docs/content/Examples.md b/docs/content/Examples.md index 1c6cf675b01..60d46662bb7 100644 --- a/docs/content/Examples.md +++ b/docs/content/Examples.md @@ -19,13 +19,13 @@ Clone Druid and build it: git clone https://github.com/metamx/druid.git druid cd druid git fetch --tags -git checkout druid-0.6.33 +git checkout druid-0.6.34 ./build.sh ``` ### Downloading the DSK (Druid Standalone Kit) -[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.33-bin.tar.gz) a stand-alone tarball and run it: +[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.34-bin.tar.gz) a stand-alone tarball and run it: ``` bash tar -xzf druid-services-0.X.X-bin.tar.gz diff --git a/docs/content/Realtime.md b/docs/content/Realtime.md index adedc001fd0..344a79424a8 100644 --- a/docs/content/Realtime.md +++ b/docs/content/Realtime.md @@ -27,7 +27,7 @@ druid.host=localhost druid.service=realtime druid.port=8083 -druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.33"] +druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.34"] druid.zk.service.host=localhost diff --git a/docs/content/Tutorial:-A-First-Look-at-Druid.md b/docs/content/Tutorial:-A-First-Look-at-Druid.md index 2de698d100d..89e30d64258 100644 --- a/docs/content/Tutorial:-A-First-Look-at-Druid.md +++ b/docs/content/Tutorial:-A-First-Look-at-Druid.md @@ -49,7 +49,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu ### Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.33-bin.tar.gz). Download this file to a directory of your choosing. +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.34-bin.tar.gz). Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -60,7 +60,7 @@ tar -zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.33 +cd druid-services-0.6.34 ``` You should see a bunch of files: diff --git a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md index f2046e7847a..d1d32554f5e 100644 --- a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md +++ b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md @@ -44,7 +44,7 @@ With real-world data, we recommend having a message bus such as [Apache Kafka](h #### Setting up Kafka -[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.33/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. +[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.34/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. Instructions for booting a Zookeeper and then Kafka cluster are available [here](http://kafka.apache.org/07/quickstart.html). diff --git a/docs/content/Tutorial:-The-Druid-Cluster.md b/docs/content/Tutorial:-The-Druid-Cluster.md index 0bc2d936716..37556d18cd5 100644 --- a/docs/content/Tutorial:-The-Druid-Cluster.md +++ b/docs/content/Tutorial:-The-Druid-Cluster.md @@ -13,7 +13,7 @@ In this tutorial, we will set up other types of Druid nodes as well as and exter If you followed the first tutorial, you should already have Druid downloaded. If not, let's go back and do that first. -You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.33-bin.tar.gz) +You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.34-bin.tar.gz) and untar the contents within by issuing: @@ -149,7 +149,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.33"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.34"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b @@ -238,7 +238,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.33","io.druid.extensions:druid-kafka-seven:0.6.33"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.34","io.druid.extensions:druid-kafka-seven:0.6.34"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/docs/content/Tutorial:-Webstream.md b/docs/content/Tutorial:-Webstream.md index 02c622d3ef8..876e777c629 100644 --- a/docs/content/Tutorial:-Webstream.md +++ b/docs/content/Tutorial:-Webstream.md @@ -37,7 +37,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.33-bin.tar.gz) +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.34-bin.tar.gz) Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -48,7 +48,7 @@ tar zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.33 +cd druid-services-0.6.34 ``` You should see a bunch of files: diff --git a/docs/content/Twitter-Tutorial.textile b/docs/content/Twitter-Tutorial.textile index c86423a8066..3b61706eba9 100644 --- a/docs/content/Twitter-Tutorial.textile +++ b/docs/content/Twitter-Tutorial.textile @@ -9,7 +9,7 @@ There are two ways to setup Druid: download a tarball, or build it from source. h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.33-bin.tar.gz. +We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.34-bin.tar.gz. Download this bad boy to a directory of your choosing. You can extract the awesomeness within by issuing: diff --git a/examples/config/historical/runtime.properties b/examples/config/historical/runtime.properties index 4a1a78f984a..e0e254da481 100644 --- a/examples/config/historical/runtime.properties +++ b/examples/config/historical/runtime.properties @@ -4,7 +4,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.33"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.34"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b diff --git a/examples/config/realtime/runtime.properties b/examples/config/realtime/runtime.properties index fb79a8fc56a..6c90686384d 100644 --- a/examples/config/realtime/runtime.properties +++ b/examples/config/realtime/runtime.properties @@ -4,7 +4,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.33","io.druid.extensions:druid-kafka-seven:0.6.33","io.druid.extensions:druid-rabbitmq:0.6.33"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.34","io.druid.extensions:druid-kafka-seven:0.6.34","io.druid.extensions:druid-rabbitmq:0.6.34"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/services/src/main/java/io/druid/cli/CliBroker.java b/services/src/main/java/io/druid/cli/CliBroker.java index 5e20ee4bf8a..dc6b95b057c 100644 --- a/services/src/main/java/io/druid/cli/CliBroker.java +++ b/services/src/main/java/io/druid/cli/CliBroker.java @@ -53,7 +53,7 @@ import java.util.List; */ @Command( name = "broker", - description = "Runs a broker node, see http://druid.io/docs/0.6.33/Broker.html for a description" + description = "Runs a broker node, see http://druid.io/docs/0.6.34/Broker.html for a description" ) public class CliBroker extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliCoordinator.java b/services/src/main/java/io/druid/cli/CliCoordinator.java index dcf638ab391..12a809a68ea 100644 --- a/services/src/main/java/io/druid/cli/CliCoordinator.java +++ b/services/src/main/java/io/druid/cli/CliCoordinator.java @@ -63,7 +63,7 @@ import java.util.List; */ @Command( name = "coordinator", - description = "Runs the Coordinator, see http://druid.io/docs/0.6.33/Coordinator.html for a description." + description = "Runs the Coordinator, see http://druid.io/docs/0.6.34/Coordinator.html for a description." ) public class CliCoordinator extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java index a6609936fac..354b5df872b 100644 --- a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java +++ b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java @@ -41,7 +41,7 @@ import java.util.List; */ @Command( name = "hadoop", - description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.33/Batch-ingestion.html for a description." + description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.34/Batch-ingestion.html for a description." ) public class CliHadoopIndexer implements Runnable { diff --git a/services/src/main/java/io/druid/cli/CliHistorical.java b/services/src/main/java/io/druid/cli/CliHistorical.java index 29d436ff7e4..a56d543a287 100644 --- a/services/src/main/java/io/druid/cli/CliHistorical.java +++ b/services/src/main/java/io/druid/cli/CliHistorical.java @@ -42,7 +42,7 @@ import java.util.List; */ @Command( name = "historical", - description = "Runs a Historical node, see http://druid.io/docs/0.6.33/Historical.html for a description" + description = "Runs a Historical node, see http://druid.io/docs/0.6.34/Historical.html for a description" ) public class CliHistorical extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index 166c6452eb9..e0d636564c4 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -96,7 +96,7 @@ import java.util.List; */ @Command( name = "overlord", - description = "Runs an Overlord node, see http://druid.io/docs/0.6.33/Indexing-Service.html for a description" + description = "Runs an Overlord node, see http://druid.io/docs/0.6.34/Indexing-Service.html for a description" ) public class CliOverlord extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtime.java b/services/src/main/java/io/druid/cli/CliRealtime.java index aa27e625538..b14f71542fb 100644 --- a/services/src/main/java/io/druid/cli/CliRealtime.java +++ b/services/src/main/java/io/druid/cli/CliRealtime.java @@ -30,7 +30,7 @@ import java.util.List; */ @Command( name = "realtime", - description = "Runs a realtime node, see http://druid.io/docs/0.6.33/Realtime.html for a description" + description = "Runs a realtime node, see http://druid.io/docs/0.6.34/Realtime.html for a description" ) public class CliRealtime extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtimeExample.java b/services/src/main/java/io/druid/cli/CliRealtimeExample.java index 6a51fdcc0d7..e941cd123c1 100644 --- a/services/src/main/java/io/druid/cli/CliRealtimeExample.java +++ b/services/src/main/java/io/druid/cli/CliRealtimeExample.java @@ -42,7 +42,7 @@ import java.util.concurrent.Executor; */ @Command( name = "realtime", - description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.33/Realtime.html for a description" + description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.34/Realtime.html for a description" ) public class CliRealtimeExample extends ServerRunnable { From 1d75738f81b4016fc9b01c73fcf2d64f5a784383 Mon Sep 17 00:00:00 2001 From: fjy Date: Fri, 13 Dec 2013 16:20:18 -0800 Subject: [PATCH 082/189] [maven-release-plugin] prepare release druid-0.6.34 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 47756cfa602..9bc08738d57 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34-SNAPSHOT + 0.6.34 diff --git a/common/pom.xml b/common/pom.xml index 2e497083c06..b4251b1b623 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34-SNAPSHOT + 0.6.34 diff --git a/examples/pom.xml b/examples/pom.xml index e221d145cf8..15ec2a8c79d 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34-SNAPSHOT + 0.6.34 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 1c646fa9f30..b246889964c 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34-SNAPSHOT + 0.6.34 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index c9cf5a0302f..0496835a39c 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34-SNAPSHOT + 0.6.34 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index ea3949c7b71..9c97f0519f4 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34-SNAPSHOT + 0.6.34 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index b41901f813c..eac7e229a08 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34-SNAPSHOT + 0.6.34 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 984ed52c9b3..74585e95b90 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34-SNAPSHOT + 0.6.34 diff --git a/pom.xml b/pom.xml index 56e856f4773..283a9ea79f8 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.34-SNAPSHOT + 0.6.34 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.34 diff --git a/processing/pom.xml b/processing/pom.xml index 14ec5a3fc29..0ad9861e706 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34-SNAPSHOT + 0.6.34 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index febab9e8ffc..dcdee6708eb 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.34-SNAPSHOT + 0.6.34 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 4853bce92d7..3124f0aa962 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34-SNAPSHOT + 0.6.34 diff --git a/server/pom.xml b/server/pom.xml index 85849f4958f..1f793d4aa1e 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34-SNAPSHOT + 0.6.34 diff --git a/services/pom.xml b/services/pom.xml index 768c1af3007..b7a50b987b2 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.34-SNAPSHOT + 0.6.34 From 7891e0a2e2697fada4a3b10fd4ada7d7c9ef60a5 Mon Sep 17 00:00:00 2001 From: fjy Date: Fri, 13 Dec 2013 16:20:23 -0800 Subject: [PATCH 083/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 9bc08738d57..6f4c930b464 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34 + 0.6.35-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index b4251b1b623..c4933498e46 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34 + 0.6.35-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index 15ec2a8c79d..d51f07ec087 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34 + 0.6.35-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index b246889964c..500e08459ce 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34 + 0.6.35-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 0496835a39c..6932978a78b 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34 + 0.6.35-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 9c97f0519f4..f9518e65fa9 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34 + 0.6.35-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index eac7e229a08..197e55eaeb7 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34 + 0.6.35-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 74585e95b90..d91e12cf646 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34 + 0.6.35-SNAPSHOT diff --git a/pom.xml b/pom.xml index 283a9ea79f8..30f4b979fe2 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.34 + 0.6.35-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.34 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index 0ad9861e706..17e8ea74388 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34 + 0.6.35-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index dcdee6708eb..b4793001c0d 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.34 + 0.6.35-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 3124f0aa962..4b0fb0e60e6 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34 + 0.6.35-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index 1f793d4aa1e..d522bb77d7d 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.34 + 0.6.35-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index b7a50b987b2..180e20fd1e6 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.34 + 0.6.35-SNAPSHOT From 01f9c1df31cdf9ac392daeb557153b920a95b181 Mon Sep 17 00:00:00 2001 From: fjy Date: Fri, 13 Dec 2013 16:45:32 -0800 Subject: [PATCH 084/189] fix broken task storage config and prepare for next release --- build.sh | 2 +- docs/content/Booting-a-production-cluster.md | 2 +- docs/content/Examples.md | 4 ++-- docs/content/Realtime.md | 2 +- docs/content/Tutorial:-A-First-Look-at-Druid.md | 4 ++-- .../Tutorial:-Loading-Your-Data-Part-2.md | 2 +- docs/content/Tutorial:-The-Druid-Cluster.md | 6 +++--- docs/content/Tutorial:-Webstream.md | 4 ++-- docs/content/Twitter-Tutorial.textile | 2 +- examples/config/historical/runtime.properties | 2 +- examples/config/realtime/runtime.properties | 2 +- .../common/config/TaskStorageConfig.java | 16 ++++------------ .../indexing/overlord/TaskLifecycleTest.java | 2 +- .../src/main/java/io/druid/cli/CliBroker.java | 2 +- .../main/java/io/druid/cli/CliCoordinator.java | 2 +- .../main/java/io/druid/cli/CliHadoopIndexer.java | 2 +- .../main/java/io/druid/cli/CliHistorical.java | 2 +- .../src/main/java/io/druid/cli/CliOverlord.java | 2 +- .../src/main/java/io/druid/cli/CliRealtime.java | 2 +- .../java/io/druid/cli/CliRealtimeExample.java | 2 +- 20 files changed, 28 insertions(+), 36 deletions(-) diff --git a/build.sh b/build.sh index a786c523be9..4e26daf7a10 100755 --- a/build.sh +++ b/build.sh @@ -30,4 +30,4 @@ echo "For examples, see: " echo " " ls -1 examples/*/*sh echo " " -echo "See also http://druid.io/docs/0.6.34" +echo "See also http://druid.io/docs/0.6.35" diff --git a/docs/content/Booting-a-production-cluster.md b/docs/content/Booting-a-production-cluster.md index b93c428eb69..9ad865e0e71 100644 --- a/docs/content/Booting-a-production-cluster.md +++ b/docs/content/Booting-a-production-cluster.md @@ -3,7 +3,7 @@ layout: doc_page --- # Booting a Single Node Cluster # -[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.34-bin.tar.gz). +[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.35-bin.tar.gz). The [ec2 run script](https://github.com/metamx/druid/blob/master/examples/bin/run_ec2.sh), run_ec2.sh, is located at 'examples/bin' if you have checked out the code, or at the root of the project if you've downloaded a tarball. The scripts rely on the [Amazon EC2 API Tools](http://aws.amazon.com/developertools/351), and you will need to set three environment variables: diff --git a/docs/content/Examples.md b/docs/content/Examples.md index 60d46662bb7..cf43c45a431 100644 --- a/docs/content/Examples.md +++ b/docs/content/Examples.md @@ -19,13 +19,13 @@ Clone Druid and build it: git clone https://github.com/metamx/druid.git druid cd druid git fetch --tags -git checkout druid-0.6.34 +git checkout druid-0.6.35 ./build.sh ``` ### Downloading the DSK (Druid Standalone Kit) -[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.34-bin.tar.gz) a stand-alone tarball and run it: +[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.35-bin.tar.gz) a stand-alone tarball and run it: ``` bash tar -xzf druid-services-0.X.X-bin.tar.gz diff --git a/docs/content/Realtime.md b/docs/content/Realtime.md index 344a79424a8..497733a6f50 100644 --- a/docs/content/Realtime.md +++ b/docs/content/Realtime.md @@ -27,7 +27,7 @@ druid.host=localhost druid.service=realtime druid.port=8083 -druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.34"] +druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.35"] druid.zk.service.host=localhost diff --git a/docs/content/Tutorial:-A-First-Look-at-Druid.md b/docs/content/Tutorial:-A-First-Look-at-Druid.md index 89e30d64258..b7c6cedcd91 100644 --- a/docs/content/Tutorial:-A-First-Look-at-Druid.md +++ b/docs/content/Tutorial:-A-First-Look-at-Druid.md @@ -49,7 +49,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu ### Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.34-bin.tar.gz). Download this file to a directory of your choosing. +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.35-bin.tar.gz). Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -60,7 +60,7 @@ tar -zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.34 +cd druid-services-0.6.35 ``` You should see a bunch of files: diff --git a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md index d1d32554f5e..fa7b8aeb542 100644 --- a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md +++ b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md @@ -44,7 +44,7 @@ With real-world data, we recommend having a message bus such as [Apache Kafka](h #### Setting up Kafka -[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.34/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. +[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.35/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. Instructions for booting a Zookeeper and then Kafka cluster are available [here](http://kafka.apache.org/07/quickstart.html). diff --git a/docs/content/Tutorial:-The-Druid-Cluster.md b/docs/content/Tutorial:-The-Druid-Cluster.md index 37556d18cd5..9d65f9c5020 100644 --- a/docs/content/Tutorial:-The-Druid-Cluster.md +++ b/docs/content/Tutorial:-The-Druid-Cluster.md @@ -13,7 +13,7 @@ In this tutorial, we will set up other types of Druid nodes as well as and exter If you followed the first tutorial, you should already have Druid downloaded. If not, let's go back and do that first. -You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.34-bin.tar.gz) +You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.35-bin.tar.gz) and untar the contents within by issuing: @@ -149,7 +149,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.34"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.35"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b @@ -238,7 +238,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.34","io.druid.extensions:druid-kafka-seven:0.6.34"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.35","io.druid.extensions:druid-kafka-seven:0.6.35"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/docs/content/Tutorial:-Webstream.md b/docs/content/Tutorial:-Webstream.md index 876e777c629..453778b820d 100644 --- a/docs/content/Tutorial:-Webstream.md +++ b/docs/content/Tutorial:-Webstream.md @@ -37,7 +37,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.34-bin.tar.gz) +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.35-bin.tar.gz) Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -48,7 +48,7 @@ tar zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.34 +cd druid-services-0.6.35 ``` You should see a bunch of files: diff --git a/docs/content/Twitter-Tutorial.textile b/docs/content/Twitter-Tutorial.textile index 3b61706eba9..bd0b2273b72 100644 --- a/docs/content/Twitter-Tutorial.textile +++ b/docs/content/Twitter-Tutorial.textile @@ -9,7 +9,7 @@ There are two ways to setup Druid: download a tarball, or build it from source. h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.34-bin.tar.gz. +We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.35-bin.tar.gz. Download this bad boy to a directory of your choosing. You can extract the awesomeness within by issuing: diff --git a/examples/config/historical/runtime.properties b/examples/config/historical/runtime.properties index e0e254da481..bb2ccc367f5 100644 --- a/examples/config/historical/runtime.properties +++ b/examples/config/historical/runtime.properties @@ -4,7 +4,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.34"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.35"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b diff --git a/examples/config/realtime/runtime.properties b/examples/config/realtime/runtime.properties index 6c90686384d..4054f98695d 100644 --- a/examples/config/realtime/runtime.properties +++ b/examples/config/realtime/runtime.properties @@ -4,7 +4,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.34","io.druid.extensions:druid-kafka-seven:0.6.34","io.druid.extensions:druid-rabbitmq:0.6.34"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.35","io.druid.extensions:druid-kafka-seven:0.6.35","io.druid.extensions:druid-rabbitmq:0.6.35"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/indexing-service/src/main/java/io/druid/indexing/common/config/TaskStorageConfig.java b/indexing-service/src/main/java/io/druid/indexing/common/config/TaskStorageConfig.java index fbbea09a8de..ca44372edad 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/config/TaskStorageConfig.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/config/TaskStorageConfig.java @@ -1,24 +1,16 @@ package io.druid.indexing.common.config; -import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import org.joda.time.Duration; import org.joda.time.Period; +import javax.validation.constraints.NotNull; + public class TaskStorageConfig { @JsonProperty - private final Duration recentlyFinishedThreshold; - - @JsonCreator - public TaskStorageConfig( - @JsonProperty("recentlyFinishedThreshold") final Period recentlyFinishedThreshold - ) - { - this.recentlyFinishedThreshold = recentlyFinishedThreshold == null - ? new Period("PT24H").toStandardDuration() - : recentlyFinishedThreshold.toStandardDuration(); - } + @NotNull + private Duration recentlyFinishedThreshold = new Period("PT24H").toStandardDuration(); public Duration getRecentlyFinishedThreshold() { diff --git a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java index 51f20e830a0..632384ceb3d 100644 --- a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java @@ -124,7 +124,7 @@ public class TaskLifecycleTest TaskQueueConfig.class ); ts = new HeapMemoryTaskStorage( - new TaskStorageConfig(new Period("PT24H")) + new TaskStorageConfig() { } ); diff --git a/services/src/main/java/io/druid/cli/CliBroker.java b/services/src/main/java/io/druid/cli/CliBroker.java index dc6b95b057c..d4437bcef0e 100644 --- a/services/src/main/java/io/druid/cli/CliBroker.java +++ b/services/src/main/java/io/druid/cli/CliBroker.java @@ -53,7 +53,7 @@ import java.util.List; */ @Command( name = "broker", - description = "Runs a broker node, see http://druid.io/docs/0.6.34/Broker.html for a description" + description = "Runs a broker node, see http://druid.io/docs/0.6.35/Broker.html for a description" ) public class CliBroker extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliCoordinator.java b/services/src/main/java/io/druid/cli/CliCoordinator.java index 12a809a68ea..289c2c13277 100644 --- a/services/src/main/java/io/druid/cli/CliCoordinator.java +++ b/services/src/main/java/io/druid/cli/CliCoordinator.java @@ -63,7 +63,7 @@ import java.util.List; */ @Command( name = "coordinator", - description = "Runs the Coordinator, see http://druid.io/docs/0.6.34/Coordinator.html for a description." + description = "Runs the Coordinator, see http://druid.io/docs/0.6.35/Coordinator.html for a description." ) public class CliCoordinator extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java index 354b5df872b..861af3c43fe 100644 --- a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java +++ b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java @@ -41,7 +41,7 @@ import java.util.List; */ @Command( name = "hadoop", - description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.34/Batch-ingestion.html for a description." + description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.35/Batch-ingestion.html for a description." ) public class CliHadoopIndexer implements Runnable { diff --git a/services/src/main/java/io/druid/cli/CliHistorical.java b/services/src/main/java/io/druid/cli/CliHistorical.java index a56d543a287..bdc041cea2e 100644 --- a/services/src/main/java/io/druid/cli/CliHistorical.java +++ b/services/src/main/java/io/druid/cli/CliHistorical.java @@ -42,7 +42,7 @@ import java.util.List; */ @Command( name = "historical", - description = "Runs a Historical node, see http://druid.io/docs/0.6.34/Historical.html for a description" + description = "Runs a Historical node, see http://druid.io/docs/0.6.35/Historical.html for a description" ) public class CliHistorical extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index e0d636564c4..5fca6dbf6cd 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -96,7 +96,7 @@ import java.util.List; */ @Command( name = "overlord", - description = "Runs an Overlord node, see http://druid.io/docs/0.6.34/Indexing-Service.html for a description" + description = "Runs an Overlord node, see http://druid.io/docs/0.6.35/Indexing-Service.html for a description" ) public class CliOverlord extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtime.java b/services/src/main/java/io/druid/cli/CliRealtime.java index b14f71542fb..a116ac6c68c 100644 --- a/services/src/main/java/io/druid/cli/CliRealtime.java +++ b/services/src/main/java/io/druid/cli/CliRealtime.java @@ -30,7 +30,7 @@ import java.util.List; */ @Command( name = "realtime", - description = "Runs a realtime node, see http://druid.io/docs/0.6.34/Realtime.html for a description" + description = "Runs a realtime node, see http://druid.io/docs/0.6.35/Realtime.html for a description" ) public class CliRealtime extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtimeExample.java b/services/src/main/java/io/druid/cli/CliRealtimeExample.java index e941cd123c1..15b8efcddf2 100644 --- a/services/src/main/java/io/druid/cli/CliRealtimeExample.java +++ b/services/src/main/java/io/druid/cli/CliRealtimeExample.java @@ -42,7 +42,7 @@ import java.util.concurrent.Executor; */ @Command( name = "realtime", - description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.34/Realtime.html for a description" + description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.35/Realtime.html for a description" ) public class CliRealtimeExample extends ServerRunnable { From 1c050a02f87623caa5db3f71c24a3527b267cb54 Mon Sep 17 00:00:00 2001 From: fjy Date: Fri, 13 Dec 2013 16:47:17 -0800 Subject: [PATCH 085/189] [maven-release-plugin] prepare release druid-0.6.35 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 6f4c930b464..ec39eb0c958 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35-SNAPSHOT + 0.6.35 diff --git a/common/pom.xml b/common/pom.xml index c4933498e46..a25d0f040ad 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35-SNAPSHOT + 0.6.35 diff --git a/examples/pom.xml b/examples/pom.xml index d51f07ec087..eb53d1e2a52 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35-SNAPSHOT + 0.6.35 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 500e08459ce..49b2b8f2651 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35-SNAPSHOT + 0.6.35 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 6932978a78b..efff2b03190 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35-SNAPSHOT + 0.6.35 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index f9518e65fa9..8b572d4bd69 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35-SNAPSHOT + 0.6.35 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 197e55eaeb7..a4d51a25313 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35-SNAPSHOT + 0.6.35 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index d91e12cf646..fddf93aeb2b 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35-SNAPSHOT + 0.6.35 diff --git a/pom.xml b/pom.xml index 30f4b979fe2..2322466d588 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.35-SNAPSHOT + 0.6.35 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.35 diff --git a/processing/pom.xml b/processing/pom.xml index 17e8ea74388..19073a4008b 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35-SNAPSHOT + 0.6.35 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index b4793001c0d..3145d82d71c 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.35-SNAPSHOT + 0.6.35 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 4b0fb0e60e6..0ae42144efb 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35-SNAPSHOT + 0.6.35 diff --git a/server/pom.xml b/server/pom.xml index d522bb77d7d..d936baa2ef7 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35-SNAPSHOT + 0.6.35 diff --git a/services/pom.xml b/services/pom.xml index 180e20fd1e6..4b95fb5ee3d 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.35-SNAPSHOT + 0.6.35 From 41d64f4aa696875701c29a61a06c812df3a79f41 Mon Sep 17 00:00:00 2001 From: fjy Date: Fri, 13 Dec 2013 16:47:21 -0800 Subject: [PATCH 086/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index ec39eb0c958..6c3601e99de 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35 + 0.6.36-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index a25d0f040ad..662cbc50249 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35 + 0.6.36-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index eb53d1e2a52..c2580523f1f 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35 + 0.6.36-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 49b2b8f2651..e07f9568d52 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35 + 0.6.36-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index efff2b03190..1d4cd60b224 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35 + 0.6.36-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 8b572d4bd69..190033cf377 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35 + 0.6.36-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index a4d51a25313..c7f97c9632c 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35 + 0.6.36-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index fddf93aeb2b..4aad747e1e0 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35 + 0.6.36-SNAPSHOT diff --git a/pom.xml b/pom.xml index 2322466d588..04395a1318b 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.35 + 0.6.36-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.35 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index 19073a4008b..f36c58d4b7e 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35 + 0.6.36-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 3145d82d71c..cabb31d5316 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.35 + 0.6.36-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 0ae42144efb..3d701845074 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35 + 0.6.36-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index d936baa2ef7..19d4c23590e 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.35 + 0.6.36-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index 4b95fb5ee3d..924d877e4cb 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.35 + 0.6.36-SNAPSHOT From 87b83bceb19a8bcb3deab239add498a1350aa347 Mon Sep 17 00:00:00 2001 From: fjy Date: Fri, 13 Dec 2013 16:55:22 -0800 Subject: [PATCH 087/189] fix task storage config serde and prepare for next release --- build.sh | 2 +- docs/content/Booting-a-production-cluster.md | 2 +- docs/content/Examples.md | 4 ++-- docs/content/Indexing-Service.md | 1 + docs/content/Realtime.md | 2 +- docs/content/Tutorial:-A-First-Look-at-Druid.md | 4 ++-- docs/content/Tutorial:-Loading-Your-Data-Part-2.md | 2 +- docs/content/Tutorial:-The-Druid-Cluster.md | 6 +++--- docs/content/Tutorial:-Webstream.md | 4 ++-- docs/content/Twitter-Tutorial.textile | 2 +- examples/config/historical/runtime.properties | 2 +- examples/config/realtime/runtime.properties | 2 +- .../io/druid/indexing/common/config/TaskStorageConfig.java | 2 +- services/src/main/java/io/druid/cli/CliBroker.java | 2 +- services/src/main/java/io/druid/cli/CliCoordinator.java | 2 +- services/src/main/java/io/druid/cli/CliHadoopIndexer.java | 2 +- services/src/main/java/io/druid/cli/CliHistorical.java | 2 +- services/src/main/java/io/druid/cli/CliOverlord.java | 3 +-- services/src/main/java/io/druid/cli/CliRealtime.java | 2 +- services/src/main/java/io/druid/cli/CliRealtimeExample.java | 2 +- 20 files changed, 25 insertions(+), 25 deletions(-) diff --git a/build.sh b/build.sh index 4e26daf7a10..e0e5f513761 100755 --- a/build.sh +++ b/build.sh @@ -30,4 +30,4 @@ echo "For examples, see: " echo " " ls -1 examples/*/*sh echo " " -echo "See also http://druid.io/docs/0.6.35" +echo "See also http://druid.io/docs/0.6.36" diff --git a/docs/content/Booting-a-production-cluster.md b/docs/content/Booting-a-production-cluster.md index 9ad865e0e71..db17223c8d5 100644 --- a/docs/content/Booting-a-production-cluster.md +++ b/docs/content/Booting-a-production-cluster.md @@ -3,7 +3,7 @@ layout: doc_page --- # Booting a Single Node Cluster # -[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.35-bin.tar.gz). +[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.36-bin.tar.gz). The [ec2 run script](https://github.com/metamx/druid/blob/master/examples/bin/run_ec2.sh), run_ec2.sh, is located at 'examples/bin' if you have checked out the code, or at the root of the project if you've downloaded a tarball. The scripts rely on the [Amazon EC2 API Tools](http://aws.amazon.com/developertools/351), and you will need to set three environment variables: diff --git a/docs/content/Examples.md b/docs/content/Examples.md index cf43c45a431..fd18b24f1b9 100644 --- a/docs/content/Examples.md +++ b/docs/content/Examples.md @@ -19,13 +19,13 @@ Clone Druid and build it: git clone https://github.com/metamx/druid.git druid cd druid git fetch --tags -git checkout druid-0.6.35 +git checkout druid-0.6.36 ./build.sh ``` ### Downloading the DSK (Druid Standalone Kit) -[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.35-bin.tar.gz) a stand-alone tarball and run it: +[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.36-bin.tar.gz) a stand-alone tarball and run it: ``` bash tar -xzf druid-services-0.X.X-bin.tar.gz diff --git a/docs/content/Indexing-Service.md b/docs/content/Indexing-Service.md index 147b4d16467..2f15b200025 100644 --- a/docs/content/Indexing-Service.md +++ b/docs/content/Indexing-Service.md @@ -117,6 +117,7 @@ In addition to the configuration of some of the default modules in [Configuratio |--------|-----------|-------| |`druid.indexer.runner.type`|Choices "local" or "remote". Indicates whether tasks should be run locally or in a distributed environment.|local| |`druid.indexer.storage.type`|Choices are "local" or "db". Indicates whether incoming tasks should be stored locally (in heap) or in a database. Storing incoming tasks in a database allows for tasks to be resumed if the overlord should fail.|local| +|`druid.indexer.storage.recentlyFinishedThreshold`|A duration of time to store task results.|PT24H| |`druid.indexer.queue.maxSize`|Maximum number of active tasks at one time.|Integer.MAX_VALUE| |`druid.indexer.queue.startDelay`|Sleep this long before starting overlord queue management. This can be useful to give a cluster time to re-orient itself after e.g. a widespread network issue.|PT1M| |`druid.indexer.queue.restartDelay`|Sleep this long when overlord queue management throws an exception before trying again.|PT30S| diff --git a/docs/content/Realtime.md b/docs/content/Realtime.md index 497733a6f50..71990ad57d9 100644 --- a/docs/content/Realtime.md +++ b/docs/content/Realtime.md @@ -27,7 +27,7 @@ druid.host=localhost druid.service=realtime druid.port=8083 -druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.35"] +druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.36"] druid.zk.service.host=localhost diff --git a/docs/content/Tutorial:-A-First-Look-at-Druid.md b/docs/content/Tutorial:-A-First-Look-at-Druid.md index b7c6cedcd91..e3fe41c51c6 100644 --- a/docs/content/Tutorial:-A-First-Look-at-Druid.md +++ b/docs/content/Tutorial:-A-First-Look-at-Druid.md @@ -49,7 +49,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu ### Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.35-bin.tar.gz). Download this file to a directory of your choosing. +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.36-bin.tar.gz). Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -60,7 +60,7 @@ tar -zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.35 +cd druid-services-0.6.36 ``` You should see a bunch of files: diff --git a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md index fa7b8aeb542..dc834ac5fd9 100644 --- a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md +++ b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md @@ -44,7 +44,7 @@ With real-world data, we recommend having a message bus such as [Apache Kafka](h #### Setting up Kafka -[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.35/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. +[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.36/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. Instructions for booting a Zookeeper and then Kafka cluster are available [here](http://kafka.apache.org/07/quickstart.html). diff --git a/docs/content/Tutorial:-The-Druid-Cluster.md b/docs/content/Tutorial:-The-Druid-Cluster.md index 9d65f9c5020..3cba7a77582 100644 --- a/docs/content/Tutorial:-The-Druid-Cluster.md +++ b/docs/content/Tutorial:-The-Druid-Cluster.md @@ -13,7 +13,7 @@ In this tutorial, we will set up other types of Druid nodes as well as and exter If you followed the first tutorial, you should already have Druid downloaded. If not, let's go back and do that first. -You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.35-bin.tar.gz) +You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.36-bin.tar.gz) and untar the contents within by issuing: @@ -149,7 +149,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.35"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.36"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b @@ -238,7 +238,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.35","io.druid.extensions:druid-kafka-seven:0.6.35"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.36","io.druid.extensions:druid-kafka-seven:0.6.36"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/docs/content/Tutorial:-Webstream.md b/docs/content/Tutorial:-Webstream.md index 453778b820d..bf201263c3a 100644 --- a/docs/content/Tutorial:-Webstream.md +++ b/docs/content/Tutorial:-Webstream.md @@ -37,7 +37,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.35-bin.tar.gz) +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.36-bin.tar.gz) Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -48,7 +48,7 @@ tar zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.35 +cd druid-services-0.6.36 ``` You should see a bunch of files: diff --git a/docs/content/Twitter-Tutorial.textile b/docs/content/Twitter-Tutorial.textile index bd0b2273b72..5a0ef90dcf7 100644 --- a/docs/content/Twitter-Tutorial.textile +++ b/docs/content/Twitter-Tutorial.textile @@ -9,7 +9,7 @@ There are two ways to setup Druid: download a tarball, or build it from source. h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.35-bin.tar.gz. +We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.36-bin.tar.gz. Download this bad boy to a directory of your choosing. You can extract the awesomeness within by issuing: diff --git a/examples/config/historical/runtime.properties b/examples/config/historical/runtime.properties index bb2ccc367f5..ce569f88f26 100644 --- a/examples/config/historical/runtime.properties +++ b/examples/config/historical/runtime.properties @@ -4,7 +4,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.35"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.36"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b diff --git a/examples/config/realtime/runtime.properties b/examples/config/realtime/runtime.properties index 4054f98695d..9ba5d55acc4 100644 --- a/examples/config/realtime/runtime.properties +++ b/examples/config/realtime/runtime.properties @@ -4,7 +4,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.35","io.druid.extensions:druid-kafka-seven:0.6.35","io.druid.extensions:druid-rabbitmq:0.6.35"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.36","io.druid.extensions:druid-kafka-seven:0.6.36","io.druid.extensions:druid-rabbitmq:0.6.36"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/indexing-service/src/main/java/io/druid/indexing/common/config/TaskStorageConfig.java b/indexing-service/src/main/java/io/druid/indexing/common/config/TaskStorageConfig.java index ca44372edad..db40c7cb069 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/config/TaskStorageConfig.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/config/TaskStorageConfig.java @@ -10,7 +10,7 @@ public class TaskStorageConfig { @JsonProperty @NotNull - private Duration recentlyFinishedThreshold = new Period("PT24H").toStandardDuration(); + public Duration recentlyFinishedThreshold = new Period("PT24H").toStandardDuration(); public Duration getRecentlyFinishedThreshold() { diff --git a/services/src/main/java/io/druid/cli/CliBroker.java b/services/src/main/java/io/druid/cli/CliBroker.java index d4437bcef0e..b099dfa69cf 100644 --- a/services/src/main/java/io/druid/cli/CliBroker.java +++ b/services/src/main/java/io/druid/cli/CliBroker.java @@ -53,7 +53,7 @@ import java.util.List; */ @Command( name = "broker", - description = "Runs a broker node, see http://druid.io/docs/0.6.35/Broker.html for a description" + description = "Runs a broker node, see http://druid.io/docs/0.6.36/Broker.html for a description" ) public class CliBroker extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliCoordinator.java b/services/src/main/java/io/druid/cli/CliCoordinator.java index 289c2c13277..13295bd3dee 100644 --- a/services/src/main/java/io/druid/cli/CliCoordinator.java +++ b/services/src/main/java/io/druid/cli/CliCoordinator.java @@ -63,7 +63,7 @@ import java.util.List; */ @Command( name = "coordinator", - description = "Runs the Coordinator, see http://druid.io/docs/0.6.35/Coordinator.html for a description." + description = "Runs the Coordinator, see http://druid.io/docs/0.6.36/Coordinator.html for a description." ) public class CliCoordinator extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java index 861af3c43fe..e873f26c70c 100644 --- a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java +++ b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java @@ -41,7 +41,7 @@ import java.util.List; */ @Command( name = "hadoop", - description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.35/Batch-ingestion.html for a description." + description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.36/Batch-ingestion.html for a description." ) public class CliHadoopIndexer implements Runnable { diff --git a/services/src/main/java/io/druid/cli/CliHistorical.java b/services/src/main/java/io/druid/cli/CliHistorical.java index bdc041cea2e..ca5449100d5 100644 --- a/services/src/main/java/io/druid/cli/CliHistorical.java +++ b/services/src/main/java/io/druid/cli/CliHistorical.java @@ -42,7 +42,7 @@ import java.util.List; */ @Command( name = "historical", - description = "Runs a Historical node, see http://druid.io/docs/0.6.35/Historical.html for a description" + description = "Runs a Historical node, see http://druid.io/docs/0.6.36/Historical.html for a description" ) public class CliHistorical extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index 5fca6dbf6cd..393b956ebd1 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -44,7 +44,6 @@ import io.druid.guice.PolyBind; import io.druid.indexing.common.actions.LocalTaskActionClientFactory; import io.druid.indexing.common.actions.TaskActionClientFactory; import io.druid.indexing.common.actions.TaskActionToolbox; -import io.druid.indexing.common.config.TaskConfig; import io.druid.indexing.common.config.TaskStorageConfig; import io.druid.indexing.common.index.ChatHandlerProvider; import io.druid.indexing.common.tasklogs.SwitchingTaskLogStreamer; @@ -96,7 +95,7 @@ import java.util.List; */ @Command( name = "overlord", - description = "Runs an Overlord node, see http://druid.io/docs/0.6.35/Indexing-Service.html for a description" + description = "Runs an Overlord node, see http://druid.io/docs/0.6.36/Indexing-Service.html for a description" ) public class CliOverlord extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtime.java b/services/src/main/java/io/druid/cli/CliRealtime.java index a116ac6c68c..065db03dba5 100644 --- a/services/src/main/java/io/druid/cli/CliRealtime.java +++ b/services/src/main/java/io/druid/cli/CliRealtime.java @@ -30,7 +30,7 @@ import java.util.List; */ @Command( name = "realtime", - description = "Runs a realtime node, see http://druid.io/docs/0.6.35/Realtime.html for a description" + description = "Runs a realtime node, see http://druid.io/docs/0.6.36/Realtime.html for a description" ) public class CliRealtime extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtimeExample.java b/services/src/main/java/io/druid/cli/CliRealtimeExample.java index 15b8efcddf2..819debc339a 100644 --- a/services/src/main/java/io/druid/cli/CliRealtimeExample.java +++ b/services/src/main/java/io/druid/cli/CliRealtimeExample.java @@ -42,7 +42,7 @@ import java.util.concurrent.Executor; */ @Command( name = "realtime", - description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.35/Realtime.html for a description" + description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.36/Realtime.html for a description" ) public class CliRealtimeExample extends ServerRunnable { From a7ff58f877ea4a762b699a433225dbba982de035 Mon Sep 17 00:00:00 2001 From: fjy Date: Fri, 13 Dec 2013 16:57:02 -0800 Subject: [PATCH 088/189] [maven-release-plugin] prepare release druid-0.6.36 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 6c3601e99de..2a610392d25 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36-SNAPSHOT + 0.6.36 diff --git a/common/pom.xml b/common/pom.xml index 662cbc50249..0bd3d534810 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36-SNAPSHOT + 0.6.36 diff --git a/examples/pom.xml b/examples/pom.xml index c2580523f1f..16114df1f22 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36-SNAPSHOT + 0.6.36 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index e07f9568d52..57a06b4f4b8 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36-SNAPSHOT + 0.6.36 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 1d4cd60b224..fd286b726b0 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36-SNAPSHOT + 0.6.36 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 190033cf377..ef2227f4e75 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36-SNAPSHOT + 0.6.36 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index c7f97c9632c..0300e858fa6 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36-SNAPSHOT + 0.6.36 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 4aad747e1e0..7dd24350701 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36-SNAPSHOT + 0.6.36 diff --git a/pom.xml b/pom.xml index 04395a1318b..1801f4b2205 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.36-SNAPSHOT + 0.6.36 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.36 diff --git a/processing/pom.xml b/processing/pom.xml index f36c58d4b7e..e537c2c56cd 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36-SNAPSHOT + 0.6.36 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index cabb31d5316..cabb6ae20dc 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.36-SNAPSHOT + 0.6.36 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 3d701845074..7f5c7f2651f 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36-SNAPSHOT + 0.6.36 diff --git a/server/pom.xml b/server/pom.xml index 19d4c23590e..dde464987d2 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36-SNAPSHOT + 0.6.36 diff --git a/services/pom.xml b/services/pom.xml index 924d877e4cb..f5e0396a1af 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.36-SNAPSHOT + 0.6.36 From 3812bf7215f6660ccf06707bff5b6de8a5c50744 Mon Sep 17 00:00:00 2001 From: fjy Date: Fri, 13 Dec 2013 16:57:07 -0800 Subject: [PATCH 089/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 2a610392d25..d2c783a089d 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36 + 0.6.37-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index 0bd3d534810..943aec92a08 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36 + 0.6.37-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index 16114df1f22..c0019b4c9cc 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36 + 0.6.37-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 57a06b4f4b8..81247522e1d 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36 + 0.6.37-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index fd286b726b0..6a5e90cd0c6 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36 + 0.6.37-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index ef2227f4e75..07974bc9ff0 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36 + 0.6.37-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 0300e858fa6..ebeba81d6d5 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36 + 0.6.37-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 7dd24350701..c7b08ebc463 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36 + 0.6.37-SNAPSHOT diff --git a/pom.xml b/pom.xml index 1801f4b2205..f970d49dea5 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.36 + 0.6.37-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.36 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index e537c2c56cd..9cf11cd9a6d 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36 + 0.6.37-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index cabb6ae20dc..29031970b84 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.36 + 0.6.37-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 7f5c7f2651f..a5b859c7ea7 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36 + 0.6.37-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index dde464987d2..bf05da8aec9 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.36 + 0.6.37-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index f5e0396a1af..306803a9ac7 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.36 + 0.6.37-SNAPSHOT From 2dd48601063f7e7df6e89cfa39dc21c78140e350 Mon Sep 17 00:00:00 2001 From: fjy Date: Fri, 13 Dec 2013 17:08:29 -0800 Subject: [PATCH 090/189] fix typo --- docs/content/Tutorial:-Loading-Your-Data-Part-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/Tutorial:-Loading-Your-Data-Part-1.md b/docs/content/Tutorial:-Loading-Your-Data-Part-1.md index 40cd012bccd..1f0992bb6bd 100644 --- a/docs/content/Tutorial:-Loading-Your-Data-Part-1.md +++ b/docs/content/Tutorial:-Loading-Your-Data-Part-1.md @@ -250,7 +250,7 @@ Issuing a [TimeBoundaryQuery](TimeBoundaryQuery.html) should yield: Console -------- -The indexing service overlord has a console located as: +The indexing service overlord has a console located at: ```bash localhost:8087/console.html From 3752122a7b4dcf53866c958b4fdf9ad17dcfb0ad Mon Sep 17 00:00:00 2001 From: Himadri Singh Date: Mon, 16 Dec 2013 15:16:28 +0530 Subject: [PATCH 091/189] implementing comments from review --- .../druid/initialization/Initialization.java | 22 +++---- .../java/io/druid/server/StatusResource.java | 60 ++++++++----------- services/src/main/java/io/druid/cli/Main.java | 3 +- .../src/main/java/io/druid/cli/Version.java | 2 +- 4 files changed, 40 insertions(+), 47 deletions(-) diff --git a/server/src/main/java/io/druid/initialization/Initialization.java b/server/src/main/java/io/druid/initialization/Initialization.java index 4051f70a7c8..48fe618a5b8 100644 --- a/server/src/main/java/io/druid/initialization/Initialization.java +++ b/server/src/main/java/io/druid/initialization/Initialization.java @@ -84,6 +84,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -101,21 +102,23 @@ public class Initialization "io.druid", "com.metamx.druid" ); - private static List loadedDruidModules = Lists.newArrayList(); + private final static Map extensionsMap = Maps.newHashMap(); /** - * @return List of {@link DruidModule}, once loaded. - */ - public static List getLoadedDruidModules() + * @param clazz Module class + * @param + * @return Returns the set of modules loaded. + */ + public static Set getLoadedModules(Class clazz) { - return loadedDruidModules; + return extensionsMap.get(clazz); } - public synchronized static List getFromExtensions(ExtensionsConfig config, Class clazz) + public synchronized static Collection getFromExtensions(ExtensionsConfig config, Class clazz) { final TeslaAether aether = getAetherClient(config); - List retVal = Lists.newArrayList(); + Set retVal = Sets.newHashSet(); if (config.searchCurrentClassloader()) { for (T module : ServiceLoader.load(clazz, Initialization.class.getClassLoader())) { @@ -141,9 +144,8 @@ public class Initialization } } - if (clazz.equals(DruidModule.class)){ - loadedDruidModules.addAll(retVal); - } + // update the map with currently loaded modules + extensionsMap.put(clazz, retVal); return retVal; } diff --git a/server/src/main/java/io/druid/server/StatusResource.java b/server/src/main/java/io/druid/server/StatusResource.java index 9a281478524..7d8ec62d611 100644 --- a/server/src/main/java/io/druid/server/StatusResource.java +++ b/server/src/main/java/io/druid/server/StatusResource.java @@ -29,6 +29,7 @@ import javax.ws.rs.Path; import javax.ws.rs.Produces; import java.util.ArrayList; import java.util.List; +import java.util.Set; /** */ @@ -39,35 +40,7 @@ public class StatusResource @Produces("application/json") public Status doGet() { - return getStatus(); - } - - public static Status getStatus() - { - return new Status( - Initialization.class.getPackage().getImplementationVersion(), - getExtensionVersions(), - new Memory(Runtime.getRuntime()) - ); - } - - /** - * Load the unique extensions and return their implementation-versions - * - * @return map of extensions loaded with their respective implementation versions. - */ - private static List getExtensionVersions() - { - final List druidModules = Initialization.getLoadedDruidModules(); - - List moduleVersions = new ArrayList<>(); - for (DruidModule module : druidModules) { - String artifact = module.getClass().getPackage().getImplementationTitle(); - String version = module.getClass().getPackage().getImplementationVersion(); - - moduleVersions.add(new ModuleVersion(module.getClass().getCanonicalName(), artifact, version)); - } - return moduleVersions; + return new Status(); } public static class Status @@ -76,13 +49,11 @@ public class StatusResource final List modules; final Memory memory; - public Status( - String version, List modules, Memory memory - ) + public Status() { - this.version = version; - this.modules = modules; - this.memory = memory; + this.version = Status.class.getPackage().getImplementationVersion(); + this.modules = getExtensionVersions(); + this.memory = new Memory(Runtime.getRuntime()); } @JsonProperty @@ -121,6 +92,25 @@ public class StatusResource } return output.toString(); } + + /** + * Load the unique extensions and return their implementation-versions + * + * @return map of extensions loaded with their respective implementation versions. + */ + private List getExtensionVersions() + { + final Set druidModules = Initialization.getLoadedModules(DruidModule.class); + List moduleVersions = new ArrayList<>(); + for (DruidModule module : druidModules) { + String artifact = module.getClass().getPackage().getImplementationTitle(); + String version = module.getClass().getPackage().getImplementationVersion(); + + moduleVersions.add(new ModuleVersion(module.getClass().getCanonicalName(), artifact, version)); + } + return moduleVersions; + } + } @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/services/src/main/java/io/druid/cli/Main.java b/services/src/main/java/io/druid/cli/Main.java index 7e9a633e8ed..8fc06ffcdc5 100644 --- a/services/src/main/java/io/druid/cli/Main.java +++ b/services/src/main/java/io/druid/cli/Main.java @@ -30,6 +30,7 @@ import io.druid.server.initialization.ExtensionsConfig; import org.apache.log4j.Level; import org.apache.log4j.Logger; +import java.util.Collection; import java.util.List; /** @@ -75,7 +76,7 @@ public class Main final Injector injector = Initialization.makeStartupInjector(); final ExtensionsConfig config = injector.getInstance(ExtensionsConfig.class); - final List extensionCommands = Initialization.getFromExtensions(config, CliCommandCreator.class); + final Collection extensionCommands = Initialization.getFromExtensions(config, CliCommandCreator.class); for (CliCommandCreator creator : extensionCommands) { creator.addCommands(builder); diff --git a/services/src/main/java/io/druid/cli/Version.java b/services/src/main/java/io/druid/cli/Version.java index 9212a3cb800..210591e81e6 100644 --- a/services/src/main/java/io/druid/cli/Version.java +++ b/services/src/main/java/io/druid/cli/Version.java @@ -31,6 +31,6 @@ public class Version implements Runnable @Override public void run() { - System.out.println(StatusResource.getStatus()); + System.out.println(new StatusResource.Status()); } } From 36756e611b462b0b41bf5ff28158516a6d0fa29f Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Mon, 16 Dec 2013 08:27:00 -0800 Subject: [PATCH 092/189] S3Utils: Fix retry predicate --- .../main/java/io/druid/storage/s3/S3Utils.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3Utils.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3Utils.java index 619ad737cfe..3ae7088d88f 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3Utils.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3Utils.java @@ -21,7 +21,7 @@ package io.druid.storage.s3; import com.google.common.base.Predicate; import com.metamx.common.RetryUtils; -import org.jets3t.service.S3ServiceException; +import org.jets3t.service.ServiceException; import org.jets3t.service.impl.rest.httpclient.RestS3Service; import org.jets3t.service.model.S3Bucket; import org.jets3t.service.model.S3Object; @@ -61,9 +61,9 @@ public class S3Utils { if (e instanceof IOException) { return true; - } else if (e instanceof S3ServiceException) { + } else if (e instanceof ServiceException) { final boolean isIOException = e.getCause() instanceof IOException; - final boolean isTimeout = "RequestTimeout".equals(((S3ServiceException) e).getS3ErrorCode()); + final boolean isTimeout = "RequestTimeout".equals(((ServiceException) e).getErrorCode()); return isIOException || isTimeout; } else { return false; @@ -75,18 +75,18 @@ public class S3Utils } public static boolean isObjectInBucket(RestS3Service s3Client, String bucketName, String objectKey) - throws S3ServiceException + throws ServiceException { try { s3Client.getObjectDetails(new S3Bucket(bucketName), objectKey); } - catch (S3ServiceException e) { + catch (ServiceException e) { if (404 == e.getResponseCode() - || "NoSuchKey".equals(e.getS3ErrorCode()) - || "NoSuchBucket".equals(e.getS3ErrorCode())) { + || "NoSuchKey".equals(e.getErrorCode()) + || "NoSuchBucket".equals(e.getErrorCode())) { return false; } - if ("AccessDenied".equals(e.getS3ErrorCode())) { + if ("AccessDenied".equals(e.getErrorCode())) { // Object is inaccessible to current user, but does exist. return true; } From 079331493a1303fa144b3e1e802194038f09ec4b Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Mon, 16 Dec 2013 10:56:14 -0800 Subject: [PATCH 093/189] ZkCoordinator: Make addSegment, addSegments idempotent --- .../server/coordination/ServerManager.java | 13 +++- .../server/coordination/ZkCoordinator.java | 62 ++++++++++--------- 2 files changed, 44 insertions(+), 31 deletions(-) diff --git a/server/src/main/java/io/druid/server/coordination/ServerManager.java b/server/src/main/java/io/druid/server/coordination/ServerManager.java index 4cbe042350a..950be651e86 100644 --- a/server/src/main/java/io/druid/server/coordination/ServerManager.java +++ b/server/src/main/java/io/druid/server/coordination/ServerManager.java @@ -116,7 +116,13 @@ public class ServerManager implements QuerySegmentWalker return segmentLoader.isSegmentLoaded(segment); } - public void loadSegment(final DataSegment segment) throws SegmentLoadingException + /** + * Load a single segment. + * @param segment segment to load + * @return true if the segment was newly loaded, false if it was already loaded + * @throws SegmentLoadingException if the segment cannot be loaded + */ + public boolean loadSegment(final DataSegment segment) throws SegmentLoadingException { final Segment adapter; try { @@ -150,8 +156,8 @@ public class ServerManager implements QuerySegmentWalker segment.getVersion() ); if ((entry != null) && (entry.getChunk(segment.getShardSpec().getPartitionNum()) != null)) { - log.info("Told to load a adapter for a segment[%s] that already exists", segment.getIdentifier()); - throw new SegmentLoadingException("Segment already exists[%s]", segment.getIdentifier()); + log.warn("Told to load a adapter for a segment[%s] that already exists", segment.getIdentifier()); + return false; } loadedIntervals.add( @@ -165,6 +171,7 @@ public class ServerManager implements QuerySegmentWalker synchronized (dataSourceCounts) { dataSourceCounts.add(dataSource, 1L); } + return true; } } diff --git a/server/src/main/java/io/druid/server/coordination/ZkCoordinator.java b/server/src/main/java/io/druid/server/coordination/ZkCoordinator.java index a55341a75a1..246415f57d0 100644 --- a/server/src/main/java/io/druid/server/coordination/ZkCoordinator.java +++ b/server/src/main/java/io/druid/server/coordination/ZkCoordinator.java @@ -230,34 +230,37 @@ public class ZkCoordinator implements DataSegmentChangeHandler try { log.info("Loading segment %s", segment.getIdentifier()); + final boolean loaded; try { - serverManager.loadSegment(segment); + loaded = serverManager.loadSegment(segment); } catch (Exception e) { removeSegment(segment); throw new SegmentLoadingException(e, "Exception loading segment[%s]", segment.getIdentifier()); } - File segmentInfoCacheFile = new File(config.getInfoDir(), segment.getIdentifier()); - if (!segmentInfoCacheFile.exists()) { + if (loaded) { + File segmentInfoCacheFile = new File(config.getInfoDir(), segment.getIdentifier()); + if (!segmentInfoCacheFile.exists()) { + try { + jsonMapper.writeValue(segmentInfoCacheFile, segment); + } + catch (IOException e) { + removeSegment(segment); + throw new SegmentLoadingException( + e, "Failed to write to disk segment info cache file[%s]", segmentInfoCacheFile + ); + } + } + try { - jsonMapper.writeValue(segmentInfoCacheFile, segment); + announcer.announceSegment(segment); } catch (IOException e) { - removeSegment(segment); - throw new SegmentLoadingException( - e, "Failed to write to disk segment info cache file[%s]", segmentInfoCacheFile - ); + throw new SegmentLoadingException(e, "Failed to announce segment[%s]", segment.getIdentifier()); } } - try { - announcer.announceSegment(segment); - } - catch (IOException e) { - throw new SegmentLoadingException(e, "Failed to announce segment[%s]", segment.getIdentifier()); - } - } catch (SegmentLoadingException e) { log.makeAlert(e, "Failed to load segment for dataSource") @@ -275,8 +278,9 @@ public class ZkCoordinator implements DataSegmentChangeHandler for (DataSegment segment : segments) { log.info("Loading segment %s", segment.getIdentifier()); + final boolean loaded; try { - serverManager.loadSegment(segment); + loaded = serverManager.loadSegment(segment); } catch (Exception e) { log.error(e, "Exception loading segment[%s]", segment.getIdentifier()); @@ -285,20 +289,22 @@ public class ZkCoordinator implements DataSegmentChangeHandler continue; } - File segmentInfoCacheFile = new File(config.getInfoDir(), segment.getIdentifier()); - if (!segmentInfoCacheFile.exists()) { - try { - jsonMapper.writeValue(segmentInfoCacheFile, segment); + if (loaded) { + File segmentInfoCacheFile = new File(config.getInfoDir(), segment.getIdentifier()); + if (!segmentInfoCacheFile.exists()) { + try { + jsonMapper.writeValue(segmentInfoCacheFile, segment); + } + catch (IOException e) { + log.error(e, "Failed to write to disk segment info cache file[%s]", segmentInfoCacheFile); + removeSegment(segment); + segmentFailures.add(segment.getIdentifier()); + continue; + } } - catch (IOException e) { - log.error(e, "Failed to write to disk segment info cache file[%s]", segmentInfoCacheFile); - removeSegment(segment); - segmentFailures.add(segment.getIdentifier()); - continue; - } - } - validSegments.add(segment); + validSegments.add(segment); + } } try { From d2451fa37bf1f62367f0c805baaa2f2e4480574c Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Mon, 16 Dec 2013 11:12:20 -0800 Subject: [PATCH 094/189] Coordinator: Stop databaseRuleManager in stopBeingLeader --- .../java/io/druid/server/coordinator/DruidCoordinator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/io/druid/server/coordinator/DruidCoordinator.java b/server/src/main/java/io/druid/server/coordinator/DruidCoordinator.java index 0ae18185015..83eac0b9646 100644 --- a/server/src/main/java/io/druid/server/coordinator/DruidCoordinator.java +++ b/server/src/main/java/io/druid/server/coordinator/DruidCoordinator.java @@ -554,8 +554,9 @@ public class DruidCoordinator } loadManagementPeons.clear(); - databaseSegmentManager.stop(); serverInventoryView.stop(); + databaseRuleManager.stop(); + databaseSegmentManager.stop(); leader = false; } catch (Exception e) { From 7759ed45178eefd7d9e827f20f7d1582377b4a5b Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Mon, 16 Dec 2013 11:12:35 -0800 Subject: [PATCH 095/189] Coordinator: Link service announcement to leadership --- .../server/coordinator/DruidCoordinator.java | 17 ++++++++++++++++- .../coordinator/DruidCoordinatorTest.java | 4 ++++ .../main/java/io/druid/cli/CliCoordinator.java | 3 --- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/io/druid/server/coordinator/DruidCoordinator.java b/server/src/main/java/io/druid/server/coordinator/DruidCoordinator.java index 83eac0b9646..71a4d0eb08c 100644 --- a/server/src/main/java/io/druid/server/coordinator/DruidCoordinator.java +++ b/server/src/main/java/io/druid/server/coordinator/DruidCoordinator.java @@ -46,10 +46,13 @@ import io.druid.client.ServerInventoryView; import io.druid.client.indexing.IndexingServiceClient; import io.druid.common.config.JacksonConfigManager; import io.druid.concurrent.Execs; +import io.druid.curator.discovery.ServiceAnnouncer; import io.druid.db.DatabaseRuleManager; import io.druid.db.DatabaseSegmentManager; import io.druid.guice.ManageLifecycle; +import io.druid.guice.annotations.Self; import io.druid.segment.IndexIO; +import io.druid.server.DruidNode; import io.druid.server.initialization.ZkPathsConfig; import io.druid.timeline.DataSegment; import org.apache.curator.framework.CuratorFramework; @@ -99,6 +102,8 @@ public class DruidCoordinator private final LoadQueueTaskMaster taskMaster; private final Map loadManagementPeons; private final AtomicReference leaderLatch; + private final ServiceAnnouncer serviceAnnouncer; + private final DruidNode self; @Inject public DruidCoordinator( @@ -112,7 +117,9 @@ public class DruidCoordinator ServiceEmitter emitter, ScheduledExecutorFactory scheduledExecutorFactory, IndexingServiceClient indexingServiceClient, - LoadQueueTaskMaster taskMaster + LoadQueueTaskMaster taskMaster, + ServiceAnnouncer serviceAnnouncer, + @Self DruidNode self ) { this( @@ -127,6 +134,8 @@ public class DruidCoordinator scheduledExecutorFactory, indexingServiceClient, taskMaster, + serviceAnnouncer, + self, Maps.newConcurrentMap() ); } @@ -143,6 +152,8 @@ public class DruidCoordinator ScheduledExecutorFactory scheduledExecutorFactory, IndexingServiceClient indexingServiceClient, LoadQueueTaskMaster taskMaster, + ServiceAnnouncer serviceAnnouncer, + DruidNode self, ConcurrentMap loadQueuePeonMap ) { @@ -157,6 +168,8 @@ public class DruidCoordinator this.emitter = emitter; this.indexingServiceClient = indexingServiceClient; this.taskMaster = taskMaster; + this.serviceAnnouncer = serviceAnnouncer; + this.self = self; this.exec = scheduledExecutorFactory.create(1, "Coordinator-Exec--%d"); @@ -474,6 +487,7 @@ public class DruidCoordinator databaseSegmentManager.start(); databaseRuleManager.start(); serverInventoryView.start(); + serviceAnnouncer.announce(self); final List> coordinatorRunnables = Lists.newArrayList(); dynamicConfigs = configManager.watch( @@ -554,6 +568,7 @@ public class DruidCoordinator } loadManagementPeons.clear(); + serviceAnnouncer.unannounce(self); serverInventoryView.stop(); databaseRuleManager.stop(); databaseSegmentManager.stop(); diff --git a/server/src/test/java/io/druid/server/coordinator/DruidCoordinatorTest.java b/server/src/test/java/io/druid/server/coordinator/DruidCoordinatorTest.java index 8f55a93948c..58323faa863 100644 --- a/server/src/test/java/io/druid/server/coordinator/DruidCoordinatorTest.java +++ b/server/src/test/java/io/druid/server/coordinator/DruidCoordinatorTest.java @@ -23,8 +23,10 @@ import com.google.common.collect.MapMaker; import com.metamx.common.concurrent.ScheduledExecutorFactory; import io.druid.client.DruidServer; import io.druid.client.SingleServerInventoryView; +import io.druid.curator.discovery.NoopServiceAnnouncer; import io.druid.curator.inventory.InventoryManagerConfig; import io.druid.db.DatabaseSegmentManager; +import io.druid.server.DruidNode; import io.druid.server.initialization.ZkPathsConfig; import io.druid.server.metrics.NoopServiceEmitter; import io.druid.timeline.DataSegment; @@ -111,6 +113,8 @@ public class DruidCoordinatorTest scheduledExecutorFactory, null, taskMaster, + new NoopServiceAnnouncer(), + new DruidNode("hey", "what", 1234), loadManagementPeons ); } diff --git a/services/src/main/java/io/druid/cli/CliCoordinator.java b/services/src/main/java/io/druid/cli/CliCoordinator.java index 13295bd3dee..1f7fc14b960 100644 --- a/services/src/main/java/io/druid/cli/CliCoordinator.java +++ b/services/src/main/java/io/druid/cli/CliCoordinator.java @@ -28,7 +28,6 @@ import com.metamx.common.concurrent.ScheduledExecutorFactory; import com.metamx.common.logger.Logger; import io.airlift.command.Command; import io.druid.client.indexing.IndexingServiceClient; -import io.druid.curator.discovery.DiscoveryModule; import io.druid.db.DatabaseRuleManager; import io.druid.db.DatabaseRuleManagerConfig; import io.druid.db.DatabaseRuleManagerProvider; @@ -41,7 +40,6 @@ import io.druid.guice.JsonConfigProvider; import io.druid.guice.LazySingleton; import io.druid.guice.LifecycleModule; import io.druid.guice.ManageLifecycle; -import io.druid.guice.annotations.Self; import io.druid.server.coordinator.DruidCoordinator; import io.druid.server.coordinator.DruidCoordinatorConfig; import io.druid.server.coordinator.LoadQueueTaskMaster; @@ -106,7 +104,6 @@ public class CliCoordinator extends ServerRunnable binder.bind(DruidCoordinator.class); LifecycleModule.register(binder, DruidCoordinator.class); - DiscoveryModule.register(binder, Self.class); binder.bind(JettyServerInitializer.class).toInstance(new CoordinatorJettyServerInitializer()); Jerseys.addResource(binder, BackwardsCompatiableInfoResource.class); From 0998d835b0644ec389106d93274cc4a78fe1bbe0 Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 16 Dec 2013 12:01:52 -0800 Subject: [PATCH 096/189] fix redirection issues with master and overlord --- ...a => BackwardsCompatibleInfoResource.java} | 4 +-- server/src/main/resources/static/index.html | 3 -- .../java/io/druid/cli/CliCoordinator.java | 9 ++---- .../main/java/io/druid/cli/CliOverlord.java | 31 ++++++++++++++----- .../CoordinatorJettyServerInitializer.java | 11 ++++--- 5 files changed, 35 insertions(+), 23 deletions(-) rename server/src/main/java/io/druid/server/http/{BackwardsCompatiableInfoResource.java => BackwardsCompatibleInfoResource.java} (93%) diff --git a/server/src/main/java/io/druid/server/http/BackwardsCompatiableInfoResource.java b/server/src/main/java/io/druid/server/http/BackwardsCompatibleInfoResource.java similarity index 93% rename from server/src/main/java/io/druid/server/http/BackwardsCompatiableInfoResource.java rename to server/src/main/java/io/druid/server/http/BackwardsCompatibleInfoResource.java index 11fe97da9be..ed1cf580887 100644 --- a/server/src/main/java/io/druid/server/http/BackwardsCompatiableInfoResource.java +++ b/server/src/main/java/io/druid/server/http/BackwardsCompatibleInfoResource.java @@ -32,10 +32,10 @@ import javax.ws.rs.Path; /** */ @Path("/static/info") -public class BackwardsCompatiableInfoResource extends InfoResource +public class BackwardsCompatibleInfoResource extends InfoResource { @Inject - public BackwardsCompatiableInfoResource( + public BackwardsCompatibleInfoResource( DruidCoordinator coordinator, InventoryView serverInventoryView, DatabaseSegmentManager databaseSegmentManager, diff --git a/server/src/main/resources/static/index.html b/server/src/main/resources/static/index.html index fb8831e29ed..cec3d620e88 100644 --- a/server/src/main/resources/static/index.html +++ b/server/src/main/resources/static/index.html @@ -32,9 +32,6 @@
-
-

Druid Version: ${pom.version} Druid API Version: ${druid.api.version}

-
diff --git a/services/src/main/java/io/druid/cli/CliCoordinator.java b/services/src/main/java/io/druid/cli/CliCoordinator.java index 13295bd3dee..c37702d9310 100644 --- a/services/src/main/java/io/druid/cli/CliCoordinator.java +++ b/services/src/main/java/io/druid/cli/CliCoordinator.java @@ -45,14 +45,13 @@ import io.druid.guice.annotations.Self; import io.druid.server.coordinator.DruidCoordinator; import io.druid.server.coordinator.DruidCoordinatorConfig; import io.druid.server.coordinator.LoadQueueTaskMaster; -import io.druid.server.http.BackwardsCompatiableInfoResource; +import io.druid.server.http.BackwardsCompatibleInfoResource; import io.druid.server.http.CoordinatorDynamicConfigsResource; import io.druid.server.http.CoordinatorRedirectInfo; import io.druid.server.http.CoordinatorResource; import io.druid.server.http.InfoResource; import io.druid.server.http.RedirectFilter; import io.druid.server.http.RedirectInfo; -import io.druid.server.http.RedirectServlet; import io.druid.server.initialization.JettyServerInitializer; import org.apache.curator.framework.CuratorFramework; import org.eclipse.jetty.server.Server; @@ -88,8 +87,8 @@ public class CliCoordinator extends ServerRunnable JsonConfigProvider.bind(binder, "druid.manager.segments", DatabaseSegmentManagerConfig.class); JsonConfigProvider.bind(binder, "druid.manager.rules", DatabaseRuleManagerConfig.class); - binder.bind(RedirectServlet.class).in(LazySingleton.class); binder.bind(RedirectFilter.class).in(LazySingleton.class); + binder.bind(RedirectInfo.class).to(CoordinatorRedirectInfo.class).in(LazySingleton.class); binder.bind(DatabaseSegmentManager.class) .toProvider(DatabaseSegmentManagerProvider.class) @@ -101,15 +100,13 @@ public class CliCoordinator extends ServerRunnable binder.bind(IndexingServiceClient.class).in(LazySingleton.class); - binder.bind(RedirectInfo.class).to(CoordinatorRedirectInfo.class).in(LazySingleton.class); - binder.bind(DruidCoordinator.class); LifecycleModule.register(binder, DruidCoordinator.class); DiscoveryModule.register(binder, Self.class); binder.bind(JettyServerInitializer.class).toInstance(new CoordinatorJettyServerInitializer()); - Jerseys.addResource(binder, BackwardsCompatiableInfoResource.class); + Jerseys.addResource(binder, BackwardsCompatibleInfoResource.class); Jerseys.addResource(binder, InfoResource.class); Jerseys.addResource(binder, CoordinatorResource.class); Jerseys.addResource(binder, CoordinatorDynamicConfigsResource.class); diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index 393b956ebd1..2c258625e4b 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -120,7 +120,11 @@ public class CliOverlord extends ServerRunnable binder.bind(TaskMaster.class).in(ManageLifecycle.class); binder.bind(TaskLogStreamer.class).to(SwitchingTaskLogStreamer.class).in(LazySingleton.class); - binder.bind(new TypeLiteral>(){}) + binder.bind( + new TypeLiteral>() + { + } + ) .toProvider( new ListProvider() .add(TaskRunnerTaskLogStreamer.class) @@ -159,7 +163,10 @@ public class CliOverlord extends ServerRunnable PolyBind.createChoice( binder, "druid.indexer.storage.type", Key.get(TaskStorage.class), Key.get(HeapMemoryTaskStorage.class) ); - final MapBinder storageBinder = PolyBind.optionBinder(binder, Key.get(TaskStorage.class)); + final MapBinder storageBinder = PolyBind.optionBinder( + binder, + Key.get(TaskStorage.class) + ); storageBinder.addBinding("local").to(HeapMemoryTaskStorage.class); binder.bind(HeapMemoryTaskStorage.class).in(LazySingleton.class); @@ -178,7 +185,10 @@ public class CliOverlord extends ServerRunnable Key.get(TaskRunnerFactory.class), Key.get(ForkingTaskRunnerFactory.class) ); - final MapBinder biddy = PolyBind.optionBinder(binder, Key.get(TaskRunnerFactory.class)); + final MapBinder biddy = PolyBind.optionBinder( + binder, + Key.get(TaskRunnerFactory.class) + ); IndexingServiceModuleHelper.configureTaskRunnerConfigs(binder); biddy.addBinding("local").to(ForkingTaskRunnerFactory.class); @@ -191,7 +201,9 @@ public class CliOverlord extends ServerRunnable private void configureAutoscale(Binder binder) { JsonConfigProvider.bind(binder, "druid.indexer.autoscale", ResourceManagementSchedulerConfig.class); - binder.bind(ResourceManagementStrategy.class).to(SimpleResourceManagementStrategy.class).in(LazySingleton.class); + binder.bind(ResourceManagementStrategy.class) + .to(SimpleResourceManagementStrategy.class) + .in(LazySingleton.class); JacksonConfigProvider.bind(binder, WorkerSetupData.CONFIG_KEY, WorkerSetupData.class, null); @@ -220,13 +232,17 @@ public class CliOverlord extends ServerRunnable } /** - */ + */ private static class OverlordJettyServerInitializer implements JettyServerInitializer { @Override public void initialize(Server server, Injector injector) { - ResourceHandler resourceHandler = new ResourceHandler(); + final ServletContextHandler redirect = new ServletContextHandler(ServletContextHandler.SESSIONS); + redirect.setContextPath("/"); + redirect.addFilter(new FilterHolder(injector.getInstance(RedirectFilter.class)), "/*", null); + + final ResourceHandler resourceHandler = new ResourceHandler(); resourceHandler.setBaseResource( new ResourceCollection( new String[]{ @@ -240,12 +256,11 @@ public class CliOverlord extends ServerRunnable root.setContextPath("/"); HandlerList handlerList = new HandlerList(); - handlerList.setHandlers(new Handler[]{resourceHandler, root, new DefaultHandler()}); + handlerList.setHandlers(new Handler[]{redirect, resourceHandler, root, new DefaultHandler()}); server.setHandler(handlerList); root.addServlet(new ServletHolder(new DefaultServlet()), "/*"); root.addFilter(GzipFilter.class, "/*", null); - root.addFilter(new FilterHolder(injector.getInstance(RedirectFilter.class)), "/*", null); root.addFilter(GuiceFilter.class, "/*", null); } } diff --git a/services/src/main/java/io/druid/cli/CoordinatorJettyServerInitializer.java b/services/src/main/java/io/druid/cli/CoordinatorJettyServerInitializer.java index bf5fcd14cc8..12cf906b3cc 100644 --- a/services/src/main/java/io/druid/cli/CoordinatorJettyServerInitializer.java +++ b/services/src/main/java/io/druid/cli/CoordinatorJettyServerInitializer.java @@ -36,25 +36,28 @@ import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlets.GzipFilter; /** -*/ + */ class CoordinatorJettyServerInitializer implements JettyServerInitializer { @Override public void initialize(Server server, Injector injector) { - ResourceHandler resourceHandler = new ResourceHandler(); + final ServletContextHandler redirect = new ServletContextHandler(ServletContextHandler.SESSIONS); + redirect.setContextPath("/"); + redirect.addFilter(new FilterHolder(injector.getInstance(RedirectFilter.class)), "/*", null); + + final ResourceHandler resourceHandler = new ResourceHandler(); resourceHandler.setResourceBase(DruidCoordinator.class.getClassLoader().getResource("static").toExternalForm()); final ServletContextHandler root = new ServletContextHandler(ServletContextHandler.SESSIONS); root.setContextPath("/"); HandlerList handlerList = new HandlerList(); - handlerList.setHandlers(new Handler[]{resourceHandler, root, new DefaultHandler()}); + handlerList.setHandlers(new Handler[]{redirect, resourceHandler, root, new DefaultHandler()}); server.setHandler(handlerList); root.addServlet(new ServletHolder(new DefaultServlet()), "/*"); root.addFilter(GzipFilter.class, "/*", null); - root.addFilter(new FilterHolder(injector.getInstance(RedirectFilter.class)), "/*", null); root.addFilter(GuiceFilter.class, "/*", null); } } From 1f7a089fa8d424cc3b77600dbd12e40451a815e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Tue, 19 Nov 2013 13:30:07 -0800 Subject: [PATCH 097/189] S3 storage: enable archiving in addition to deleting unused segments --- .../druid/storage/s3/S3DataSegmentKiller.java | 36 ++++++++++++++++--- .../storage/s3/S3DataSegmentKillerConfig.java | 22 ++++++++++++ .../storage/s3/S3StorageDruidModule.java | 1 + 3 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKillerConfig.java diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKiller.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKiller.java index b9a44f631c6..cc9afbb18d9 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKiller.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKiller.java @@ -27,6 +27,7 @@ import io.druid.segment.loading.SegmentLoadingException; import io.druid.timeline.DataSegment; import org.jets3t.service.ServiceException; import org.jets3t.service.impl.rest.httpclient.RestS3Service; +import org.jets3t.service.model.S3Object; import java.util.Map; @@ -37,13 +38,16 @@ public class S3DataSegmentKiller implements DataSegmentKiller private static final Logger log = new Logger(S3DataSegmentKiller.class); private final RestS3Service s3Client; + private final S3DataSegmentKillerConfig config; @Inject public S3DataSegmentKiller( - RestS3Service s3Client + RestS3Service s3Client, + S3DataSegmentKillerConfig config ) { this.s3Client = s3Client; + this.config = config; } @Override @@ -54,14 +58,36 @@ public class S3DataSegmentKiller implements DataSegmentKiller String s3Bucket = MapUtils.getString(loadSpec, "bucket"); String s3Path = MapUtils.getString(loadSpec, "key"); String s3DescriptorPath = s3Path.substring(0, s3Path.lastIndexOf("/")) + "/descriptor.json"; + final String s3ArchiveBucket = config.getArchiveBucket(); if (s3Client.isObjectInBucket(s3Bucket, s3Path)) { - log.info("Removing index file[s3://%s/%s] from s3!", s3Bucket, s3Path); - s3Client.deleteObject(s3Bucket, s3Path); + if (config.isArchive()) { + log.info("Archiving index file[s3://%s/%s] to [s3://%s/%s]", + s3Bucket, + s3Path, + s3ArchiveBucket, + s3Path + ); + s3Client.moveObject(s3Bucket, s3Path, s3ArchiveBucket, new S3Object(s3Path), false); + } else { + log.info("Removing index file[s3://%s/%s] from s3!", s3Bucket, s3Path); + s3Client.deleteObject(s3Bucket, s3Path); + } } if (s3Client.isObjectInBucket(s3Bucket, s3DescriptorPath)) { - log.info("Removing descriptor file[s3://%s/%s] from s3!", s3Bucket, s3DescriptorPath); - s3Client.deleteObject(s3Bucket, s3DescriptorPath); + if (config.isArchive()) { + log.info( + "Archiving descriptor file[s3://%s/%s] to [s3://%s/%s]", + s3Bucket, + s3DescriptorPath, + s3ArchiveBucket, + s3DescriptorPath + ); + s3Client.moveObject(s3Bucket, s3DescriptorPath, s3ArchiveBucket, new S3Object(s3DescriptorPath), false); + } else { + log.info("Removing descriptor file[s3://%s/%s] from s3!", s3Bucket, s3DescriptorPath); + s3Client.deleteObject(s3Bucket, s3DescriptorPath); + } } } catch (ServiceException e) { diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKillerConfig.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKillerConfig.java new file mode 100644 index 00000000000..b17e87ac2be --- /dev/null +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKillerConfig.java @@ -0,0 +1,22 @@ +package io.druid.storage.s3; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class S3DataSegmentKillerConfig +{ + @JsonProperty + public boolean archive = false; + + @JsonProperty + public String archiveBucket = ""; + + public boolean isArchive() + { + return archive; + } + + public String getArchiveBucket() + { + return archiveBucket; + } +} diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3StorageDruidModule.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3StorageDruidModule.java index f2675251f19..6bb4624a3d3 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3StorageDruidModule.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3StorageDruidModule.java @@ -53,6 +53,7 @@ public class S3StorageDruidModule implements DruidModule Binders.dataSegmentKillerBinder(binder).addBinding("s3_zip").to(S3DataSegmentKiller.class).in(LazySingleton.class); Binders.dataSegmentPusherBinder(binder).addBinding("s3").to(S3DataSegmentPusher.class).in(LazySingleton.class); JsonConfigProvider.bind(binder, "druid.storage", S3DataSegmentPusherConfig.class); + JsonConfigProvider.bind(binder, "druid.storage", S3DataSegmentKillerConfig.class); Binders.taskLogsBinder(binder).addBinding("s3").to(S3TaskLogs.class); JsonConfigProvider.bind(binder, "druid.indexer.logs", S3TaskLogsConfig.class); From e38f2877fbb085e5e87706b9aeb0417315d9fd6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Tue, 19 Nov 2013 14:14:52 -0800 Subject: [PATCH 098/189] default to archiving segments + docs --- docs/content/Configuration.md | 2 ++ .../main/java/io/druid/storage/s3/S3DataSegmentKiller.java | 6 ++++++ .../java/io/druid/storage/s3/S3DataSegmentKillerConfig.java | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/content/Configuration.md b/docs/content/Configuration.md index d7e010b3006..e5c64c74c17 100644 --- a/docs/content/Configuration.md +++ b/docs/content/Configuration.md @@ -287,6 +287,8 @@ This deep storage is used to interface with Amazon's S3. |`druid.storage.bucket`|S3 bucket name.|none| |`druid.storage.basekey`|S3 base key.|none| |`druid.storage.disableAcl`|Boolean flag for ACL.|false| +|`druid.storage.archive`|Boolean flag. Archives killed segments instead of deleting them from S3|true| +|`druid.storage.archiveBucket`|S3 bucket name to archive segments to|none| #### HDFS Deep Storage diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKiller.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKiller.java index cc9afbb18d9..5409568a917 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKiller.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKiller.java @@ -58,8 +58,14 @@ public class S3DataSegmentKiller implements DataSegmentKiller String s3Bucket = MapUtils.getString(loadSpec, "bucket"); String s3Path = MapUtils.getString(loadSpec, "key"); String s3DescriptorPath = s3Path.substring(0, s3Path.lastIndexOf("/")) + "/descriptor.json"; + final String s3ArchiveBucket = config.getArchiveBucket(); + if(config.isArchive() && s3ArchiveBucket.isEmpty()) { + log.warn("S3 archive bucket not specified, refusing to delete segment [s3://%s/%s]", s3Bucket, s3Path); + return; + } + if (s3Client.isObjectInBucket(s3Bucket, s3Path)) { if (config.isArchive()) { log.info("Archiving index file[s3://%s/%s] to [s3://%s/%s]", diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKillerConfig.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKillerConfig.java index b17e87ac2be..bd169b05f2b 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKillerConfig.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKillerConfig.java @@ -5,7 +5,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; public class S3DataSegmentKillerConfig { @JsonProperty - public boolean archive = false; + public boolean archive = true; @JsonProperty public String archiveBucket = ""; From a417cd5df2c1b9121c067ee17140fb96ca9a590b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Thu, 5 Dec 2013 17:37:03 -0800 Subject: [PATCH 099/189] add archive task --- .../io/druid/indexing/common/TaskToolbox.java | 9 ++ .../indexing/common/TaskToolboxFactory.java | 6 +- .../common/actions/SegmentMoveAction.java | 77 ++++++++++++ .../indexing/common/actions/TaskAction.java | 3 +- .../druid/indexing/common/task/MoveTask.java | 110 ++++++++++++++++++ .../io/druid/indexing/common/task/Task.java | 1 + .../overlord/IndexerDBCoordinator.java | 39 ++++++- .../indexing/overlord/TaskLifecycleTest.java | 9 ++ .../worker/WorkerTaskMonitorTest.java | 4 +- .../druid/storage/s3/S3DataSegmentKiller.java | 41 +------ .../storage/s3/S3DataSegmentKillerConfig.java | 22 ---- .../druid/storage/s3/S3DataSegmentMover.java | 99 ++++++++++++++++ .../storage/s3/S3DataSegmentMoverConfig.java | 33 ++++++ .../storage/s3/S3StorageDruidModule.java | 3 +- .../segment/loading/OmniDataSegmentMover.java | 57 +++++++++ .../src/main/java/io/druid/cli/CliPeon.java | 4 + 16 files changed, 451 insertions(+), 66 deletions(-) create mode 100644 indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentMoveAction.java create mode 100644 indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java delete mode 100644 s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKillerConfig.java create mode 100644 s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java create mode 100644 s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMoverConfig.java create mode 100644 server/src/main/java/io/druid/segment/loading/OmniDataSegmentMover.java diff --git a/indexing-service/src/main/java/io/druid/indexing/common/TaskToolbox.java b/indexing-service/src/main/java/io/druid/indexing/common/TaskToolbox.java index 0a7a505d4ec..56f08f3c9f3 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/TaskToolbox.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/TaskToolbox.java @@ -30,6 +30,7 @@ import io.druid.indexing.common.config.TaskConfig; import io.druid.indexing.common.task.Task; import io.druid.query.QueryRunnerFactoryConglomerate; import io.druid.segment.loading.DataSegmentKiller; +import io.druid.segment.loading.DataSegmentMover; import io.druid.segment.loading.DataSegmentPusher; import io.druid.segment.loading.SegmentLoader; import io.druid.segment.loading.SegmentLoadingException; @@ -52,6 +53,7 @@ public class TaskToolbox private final ServiceEmitter emitter; private final DataSegmentPusher segmentPusher; private final DataSegmentKiller dataSegmentKiller; + private final DataSegmentMover dataSegmentMover; private final DataSegmentAnnouncer segmentAnnouncer; private final ServerView newSegmentServerView; private final QueryRunnerFactoryConglomerate queryRunnerFactoryConglomerate; @@ -68,6 +70,7 @@ public class TaskToolbox ServiceEmitter emitter, DataSegmentPusher segmentPusher, DataSegmentKiller dataSegmentKiller, + DataSegmentMover dataSegmentMover, DataSegmentAnnouncer segmentAnnouncer, ServerView newSegmentServerView, QueryRunnerFactoryConglomerate queryRunnerFactoryConglomerate, @@ -84,6 +87,7 @@ public class TaskToolbox this.emitter = emitter; this.segmentPusher = segmentPusher; this.dataSegmentKiller = dataSegmentKiller; + this.dataSegmentMover = dataSegmentMover; this.segmentAnnouncer = segmentAnnouncer; this.newSegmentServerView = newSegmentServerView; this.queryRunnerFactoryConglomerate = queryRunnerFactoryConglomerate; @@ -119,6 +123,11 @@ public class TaskToolbox return dataSegmentKiller; } + public DataSegmentMover getDataSegmentMover() + { + return dataSegmentMover; + } + public DataSegmentAnnouncer getSegmentAnnouncer() { return segmentAnnouncer; diff --git a/indexing-service/src/main/java/io/druid/indexing/common/TaskToolboxFactory.java b/indexing-service/src/main/java/io/druid/indexing/common/TaskToolboxFactory.java index ca00dccaf91..cf9614c4bd6 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/TaskToolboxFactory.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/TaskToolboxFactory.java @@ -24,13 +24,13 @@ import com.google.inject.Inject; import com.metamx.emitter.service.ServiceEmitter; import com.metamx.metrics.MonitorScheduler; import io.druid.client.ServerView; -import io.druid.guice.annotations.Json; import io.druid.guice.annotations.Processing; import io.druid.indexing.common.actions.TaskActionClientFactory; import io.druid.indexing.common.config.TaskConfig; import io.druid.indexing.common.task.Task; import io.druid.query.QueryRunnerFactoryConglomerate; import io.druid.segment.loading.DataSegmentKiller; +import io.druid.segment.loading.DataSegmentMover; import io.druid.segment.loading.DataSegmentPusher; import io.druid.server.coordination.DataSegmentAnnouncer; @@ -47,6 +47,7 @@ public class TaskToolboxFactory private final ServiceEmitter emitter; private final DataSegmentPusher segmentPusher; private final DataSegmentKiller dataSegmentKiller; + private final DataSegmentMover dataSegmentMover; private final DataSegmentAnnouncer segmentAnnouncer; private final ServerView newSegmentServerView; private final QueryRunnerFactoryConglomerate queryRunnerFactoryConglomerate; @@ -62,6 +63,7 @@ public class TaskToolboxFactory ServiceEmitter emitter, DataSegmentPusher segmentPusher, DataSegmentKiller dataSegmentKiller, + DataSegmentMover dataSegmentMover, DataSegmentAnnouncer segmentAnnouncer, ServerView newSegmentServerView, QueryRunnerFactoryConglomerate queryRunnerFactoryConglomerate, @@ -76,6 +78,7 @@ public class TaskToolboxFactory this.emitter = emitter; this.segmentPusher = segmentPusher; this.dataSegmentKiller = dataSegmentKiller; + this.dataSegmentMover = dataSegmentMover; this.segmentAnnouncer = segmentAnnouncer; this.newSegmentServerView = newSegmentServerView; this.queryRunnerFactoryConglomerate = queryRunnerFactoryConglomerate; @@ -96,6 +99,7 @@ public class TaskToolboxFactory emitter, segmentPusher, dataSegmentKiller, + dataSegmentMover, segmentAnnouncer, newSegmentServerView, queryRunnerFactoryConglomerate, diff --git a/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentMoveAction.java b/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentMoveAction.java new file mode 100644 index 00000000000..67db4fc79be --- /dev/null +++ b/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentMoveAction.java @@ -0,0 +1,77 @@ +package io.druid.indexing.common.actions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.common.collect.ImmutableSet; +import com.metamx.common.ISE; +import com.metamx.emitter.service.ServiceMetricEvent; +import io.druid.indexing.common.task.Task; +import io.druid.timeline.DataSegment; + +import java.io.IOException; +import java.util.Set; + +public class SegmentMoveAction implements TaskAction +{ + @JsonIgnore + private final Set segments; + + @JsonCreator + public SegmentMoveAction( + @JsonProperty("segments") Set segments + ) + { + this.segments = ImmutableSet.copyOf(segments); + } + + @JsonProperty + public Set getSegments() + { + return segments; + } + + public TypeReference getReturnTypeReference() + { + return new TypeReference() {}; + } + + @Override + public Void perform( + Task task, TaskActionToolbox toolbox + ) throws IOException + { + if(!toolbox.taskLockCoversSegments(task, segments, true)) { + throw new ISE("Segments not covered by locks for task: %s", task.getId()); + } + + toolbox.getIndexerDBCoordinator().moveSegments(segments); + + // Emit metrics + final ServiceMetricEvent.Builder metricBuilder = new ServiceMetricEvent.Builder() + .setUser2(task.getDataSource()) + .setUser4(task.getType()); + + for (DataSegment segment : segments) { + metricBuilder.setUser5(segment.getInterval().toString()); + toolbox.getEmitter().emit(metricBuilder.build("indexer/segmentMoved/bytes", segment.getSize())); + } + + return null; + } + + @Override + public boolean isAudited() + { + return true; + } + + @Override + public String toString() + { + return "SegmentMoveAction{" + + "segments=" + segments + + '}'; + } +} diff --git a/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskAction.java b/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskAction.java index d9bdfe5b694..1d59e40c6c3 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskAction.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskAction.java @@ -35,7 +35,8 @@ import java.io.IOException; @JsonSubTypes.Type(name = "segmentInsertion", value = SegmentInsertAction.class), @JsonSubTypes.Type(name = "segmentListUsed", value = SegmentListUsedAction.class), @JsonSubTypes.Type(name = "segmentListUnused", value = SegmentListUnusedAction.class), - @JsonSubTypes.Type(name = "segmentNuke", value = SegmentNukeAction.class) + @JsonSubTypes.Type(name = "segmentNuke", value = SegmentNukeAction.class), + @JsonSubTypes.Type(name = "segmentMove", value = SegmentMoveAction.class) }) public interface TaskAction { diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java new file mode 100644 index 00000000000..4ca600f8ea5 --- /dev/null +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java @@ -0,0 +1,110 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.indexing.common.task; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.metamx.common.ISE; +import com.metamx.common.logger.Logger; +import io.druid.indexing.common.TaskLock; +import io.druid.indexing.common.TaskStatus; +import io.druid.indexing.common.TaskToolbox; +import io.druid.indexing.common.actions.SegmentListUnusedAction; +import io.druid.indexing.common.actions.SegmentMoveAction; +import io.druid.timeline.DataSegment; +import org.joda.time.Interval; + +import java.util.List; + +public class MoveTask extends AbstractTask +{ + private static final Logger log = new Logger(MoveTask.class); + + @JsonCreator + public MoveTask( + @JsonProperty("id") String id, + @JsonProperty("dataSource") String dataSource, + @JsonProperty("interval") Interval interval + ) + { + super( + TaskUtils.makeId(id, "move", dataSource, interval), + dataSource, + interval + ); + } + + @Override + public String getType() + { + return "move"; + } + + @Override + public TaskStatus run(TaskToolbox toolbox) throws Exception + { + // Confirm we have a lock (will throw if there isn't exactly one element) + final TaskLock myLock = Iterables.getOnlyElement(getTaskLocks(toolbox)); + + if(!myLock.getDataSource().equals(getDataSource())) { + throw new ISE("WTF?! Lock dataSource[%s] != task dataSource[%s]", myLock.getDataSource(), getDataSource()); + } + + if(!myLock.getInterval().equals(getImplicitLockInterval().get())) { + throw new ISE("WTF?! Lock interval[%s] != task interval[%s]", myLock.getInterval(), getImplicitLockInterval().get()); + } + + // List unused segments + final List unusedSegments = toolbox + .getTaskActionClient() + .submit(new SegmentListUnusedAction(myLock.getDataSource(), myLock.getInterval())); + + // Verify none of these segments have versions > lock version + for(final DataSegment unusedSegment : unusedSegments) { + if(unusedSegment.getVersion().compareTo(myLock.getVersion()) > 0) { + throw new ISE( + "WTF?! Unused segment[%s] has version[%s] > task version[%s]", + unusedSegment.getIdentifier(), + unusedSegment.getVersion(), + myLock.getVersion() + ); + } + + log.info("OK to move segment: %s", unusedSegment.getIdentifier()); + } + + List movedSegments = Lists.newLinkedList(); + + // Move segments + for (DataSegment segment : unusedSegments) { + movedSegments.add(toolbox.getDataSegmentMover().move(segment)); + } + + // Update metadata for moved segments + toolbox.getTaskActionClient().submit(new SegmentMoveAction( + ImmutableSet.copyOf(movedSegments) + )); + + return TaskStatus.success(getId()); + } +} diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/Task.java b/indexing-service/src/main/java/io/druid/indexing/common/task/Task.java index 4d6afd2ebf6..32747bdc7d3 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/Task.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/Task.java @@ -45,6 +45,7 @@ import io.druid.query.QueryRunner; @JsonSubTypes.Type(name = "merge", value = MergeTask.class), @JsonSubTypes.Type(name = "delete", value = DeleteTask.class), @JsonSubTypes.Type(name = "kill", value = KillTask.class), + @JsonSubTypes.Type(name = "move", value = MoveTask.class), @JsonSubTypes.Type(name = "index", value = IndexTask.class), @JsonSubTypes.Type(name = "index_hadoop", value = HadoopIndexTask.class), @JsonSubTypes.Type(name = "index_realtime", value = RealtimeIndexTask.class), diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/IndexerDBCoordinator.java b/indexing-service/src/main/java/io/druid/indexing/overlord/IndexerDBCoordinator.java index 2a4b5e8912d..943e63fa821 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/IndexerDBCoordinator.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/IndexerDBCoordinator.java @@ -28,8 +28,6 @@ import com.google.common.collect.Ordering; import com.google.common.collect.Sets; import com.google.inject.Inject; import com.metamx.common.logger.Logger; -import io.druid.db.DbConnector; -import io.druid.db.DbConnectorConfig; import io.druid.db.DbTablesConfig; import io.druid.timeline.DataSegment; import io.druid.timeline.TimelineObjectHolder; @@ -213,6 +211,24 @@ public class IndexerDBCoordinator return true; } + public void moveSegments(final Set segments) throws IOException + { + dbi.inTransaction( + new TransactionCallback() + { + @Override + public Void inTransaction(Handle handle, TransactionStatus transactionStatus) throws Exception + { + for(final DataSegment segment : segments) { + moveSegment(handle, segment); + } + + return null; + } + } + ); + } + public void deleteSegments(final Set segments) throws IOException { dbi.inTransaction( @@ -235,10 +251,27 @@ public class IndexerDBCoordinator { handle.createStatement( String.format("DELETE from %s WHERE id = :id", dbTables.getSegmentsTable()) - ).bind("id", segment.getIdentifier()) + ) + .bind("id", segment.getIdentifier()) .execute(); } + private void moveSegment(final Handle handle, final DataSegment segment) throws IOException + { + try { + handle.createStatement( + String.format("UPDATE %s SET payload = :payload WHERE id = :id", dbTables.getSegmentsTable()) + ) + .bind("id", segment.getIdentifier()) + .bind("payload", jsonMapper.writeValueAsString(segment)) + .execute(); + } + catch (IOException e) { + log.error(e, "Exception inserting into DB"); + throw e; + } + } + public List getUnusedSegmentsForInterval(final String dataSource, final Interval interval) { List matchingSegments = dbi.withHandle( diff --git a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java index 632384ceb3d..fcbfed7589b 100644 --- a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java @@ -65,6 +65,7 @@ import io.druid.jackson.DefaultObjectMapper; import io.druid.query.aggregation.AggregatorFactory; import io.druid.query.aggregation.DoubleSumAggregatorFactory; import io.druid.segment.loading.DataSegmentKiller; +import io.druid.segment.loading.DataSegmentMover; import io.druid.segment.loading.DataSegmentPuller; import io.druid.segment.loading.DataSegmentPusher; import io.druid.segment.loading.LocalDataSegmentPuller; @@ -158,6 +159,14 @@ public class TaskLifecycleTest } }, + new DataSegmentMover() + { + @Override + public DataSegment move(DataSegment dataSegment) throws SegmentLoadingException + { + return dataSegment; + } + }, null, // segment announcer null, // new segment server view null, // query runner factory conglomerate corporation unionized collective diff --git a/indexing-service/src/test/java/io/druid/indexing/worker/WorkerTaskMonitorTest.java b/indexing-service/src/test/java/io/druid/indexing/worker/WorkerTaskMonitorTest.java index f80ca3cd8db..7723013d239 100644 --- a/indexing-service/src/test/java/io/druid/indexing/worker/WorkerTaskMonitorTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/worker/WorkerTaskMonitorTest.java @@ -122,7 +122,7 @@ public class WorkerTaskMonitorTest new ThreadPoolTaskRunner( new TaskToolboxFactory( new TaskConfig(tmp.toString(), null, null, 0), - null, null, null, null, null, null, null, null, null, new SegmentLoaderFactory( + null, null, null, null, null, null, null, null, null, null, new SegmentLoaderFactory( new OmniSegmentLoader( ImmutableMap.of( "local", @@ -209,4 +209,4 @@ public class WorkerTaskMonitorTest Assert.assertEquals(task.getId(), taskAnnouncement.getTaskStatus().getId()); Assert.assertEquals(TaskStatus.Status.RUNNING, taskAnnouncement.getTaskStatus().getStatusCode()); } -} \ No newline at end of file +} diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKiller.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKiller.java index 5409568a917..4e295d17aa4 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKiller.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKiller.java @@ -38,16 +38,13 @@ public class S3DataSegmentKiller implements DataSegmentKiller private static final Logger log = new Logger(S3DataSegmentKiller.class); private final RestS3Service s3Client; - private final S3DataSegmentKillerConfig config; @Inject public S3DataSegmentKiller( - RestS3Service s3Client, - S3DataSegmentKillerConfig config + RestS3Service s3Client ) { this.s3Client = s3Client; - this.config = config; } @Override @@ -59,41 +56,13 @@ public class S3DataSegmentKiller implements DataSegmentKiller String s3Path = MapUtils.getString(loadSpec, "key"); String s3DescriptorPath = s3Path.substring(0, s3Path.lastIndexOf("/")) + "/descriptor.json"; - final String s3ArchiveBucket = config.getArchiveBucket(); - - if(config.isArchive() && s3ArchiveBucket.isEmpty()) { - log.warn("S3 archive bucket not specified, refusing to delete segment [s3://%s/%s]", s3Bucket, s3Path); - return; - } - if (s3Client.isObjectInBucket(s3Bucket, s3Path)) { - if (config.isArchive()) { - log.info("Archiving index file[s3://%s/%s] to [s3://%s/%s]", - s3Bucket, - s3Path, - s3ArchiveBucket, - s3Path - ); - s3Client.moveObject(s3Bucket, s3Path, s3ArchiveBucket, new S3Object(s3Path), false); - } else { - log.info("Removing index file[s3://%s/%s] from s3!", s3Bucket, s3Path); - s3Client.deleteObject(s3Bucket, s3Path); - } + log.info("Removing index file[s3://%s/%s] from s3!", s3Bucket, s3Path); + s3Client.deleteObject(s3Bucket, s3Path); } if (s3Client.isObjectInBucket(s3Bucket, s3DescriptorPath)) { - if (config.isArchive()) { - log.info( - "Archiving descriptor file[s3://%s/%s] to [s3://%s/%s]", - s3Bucket, - s3DescriptorPath, - s3ArchiveBucket, - s3DescriptorPath - ); - s3Client.moveObject(s3Bucket, s3DescriptorPath, s3ArchiveBucket, new S3Object(s3DescriptorPath), false); - } else { - log.info("Removing descriptor file[s3://%s/%s] from s3!", s3Bucket, s3DescriptorPath); - s3Client.deleteObject(s3Bucket, s3DescriptorPath); - } + log.info("Removing descriptor file[s3://%s/%s] from s3!", s3Bucket, s3DescriptorPath); + s3Client.deleteObject(s3Bucket, s3DescriptorPath); } } catch (ServiceException e) { diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKillerConfig.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKillerConfig.java deleted file mode 100644 index bd169b05f2b..00000000000 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKillerConfig.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.druid.storage.s3; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class S3DataSegmentKillerConfig -{ - @JsonProperty - public boolean archive = true; - - @JsonProperty - public String archiveBucket = ""; - - public boolean isArchive() - { - return archive; - } - - public String getArchiveBucket() - { - return archiveBucket; - } -} diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java new file mode 100644 index 00000000000..245cbab8a63 --- /dev/null +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java @@ -0,0 +1,99 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.storage.s3; + +import com.google.common.collect.ImmutableMap; +import com.google.inject.Inject; +import com.metamx.common.MapUtils; +import com.metamx.common.logger.Logger; +import io.druid.segment.loading.DataSegmentMover; +import io.druid.segment.loading.SegmentLoadingException; +import io.druid.timeline.DataSegment; +import org.jets3t.service.ServiceException; +import org.jets3t.service.impl.rest.httpclient.RestS3Service; +import org.jets3t.service.model.S3Object; + +import java.util.Map; + +public class S3DataSegmentMover implements DataSegmentMover +{ + private static final Logger log = new Logger(S3DataSegmentKiller.class); + + private final RestS3Service s3Client; + private final S3DataSegmentMoverConfig config; + + @Inject + public S3DataSegmentMover( + RestS3Service s3Client, + S3DataSegmentMoverConfig config + ) + { + this.s3Client = s3Client; + this.config = config; + } + + @Override + public DataSegment move(DataSegment segment) throws SegmentLoadingException + { + try { + Map loadSpec = segment.getLoadSpec(); + String s3Bucket = MapUtils.getString(loadSpec, "bucket"); + String s3Path = MapUtils.getString(loadSpec, "key"); + String s3DescriptorPath = s3Path.substring(0, s3Path.lastIndexOf("/")) + "/descriptor.json"; + + final String s3ArchiveBucket = config.getArchiveBucket(); + + if (s3ArchiveBucket.isEmpty()) { + log.warn("S3 archive bucket not specified, refusing to move segment [s3://%s/%s]", s3Bucket, s3Path); + return segment; + } + + if (s3Client.isObjectInBucket(s3Bucket, s3Path)) { + log.info( + "Moving index file[s3://%s/%s] to [s3://%s/%s]", + s3Bucket, + s3Path, + s3ArchiveBucket, + s3Path + ); + s3Client.moveObject(s3Bucket, s3Path, s3ArchiveBucket, new S3Object(s3Path), false); + } + if (s3Client.isObjectInBucket(s3Bucket, s3DescriptorPath)) { + log.info( + "Moving descriptor file[s3://%s/%s] to [s3://%s/%s]", + s3Bucket, + s3DescriptorPath, + s3ArchiveBucket, + s3DescriptorPath + ); + s3Client.moveObject(s3Bucket, s3DescriptorPath, s3ArchiveBucket, new S3Object(s3DescriptorPath), false); + } + + return segment.withLoadSpec( + ImmutableMap.builder() + .putAll(loadSpec) + .put("bucket", s3ArchiveBucket).build() + ); + } + catch (ServiceException e) { + throw new SegmentLoadingException(e, "Unable to move segment[%s]", segment.getIdentifier()); + } + } +} diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMoverConfig.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMoverConfig.java new file mode 100644 index 00000000000..a298ba9da39 --- /dev/null +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMoverConfig.java @@ -0,0 +1,33 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.storage.s3; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class S3DataSegmentMoverConfig +{ + @JsonProperty + public String archiveBucket = ""; + + public String getArchiveBucket() + { + return archiveBucket; + } +} diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3StorageDruidModule.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3StorageDruidModule.java index 6bb4624a3d3..fadba584bce 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3StorageDruidModule.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3StorageDruidModule.java @@ -51,9 +51,10 @@ public class S3StorageDruidModule implements DruidModule Binders.dataSegmentPullerBinder(binder).addBinding("s3_zip").to(S3DataSegmentPuller.class).in(LazySingleton.class); Binders.dataSegmentKillerBinder(binder).addBinding("s3_zip").to(S3DataSegmentKiller.class).in(LazySingleton.class); + Binders.dataSegmentMoverBinder(binder).addBinding("s3_zip").to(S3DataSegmentMover.class).in(LazySingleton.class); Binders.dataSegmentPusherBinder(binder).addBinding("s3").to(S3DataSegmentPusher.class).in(LazySingleton.class); JsonConfigProvider.bind(binder, "druid.storage", S3DataSegmentPusherConfig.class); - JsonConfigProvider.bind(binder, "druid.storage", S3DataSegmentKillerConfig.class); + JsonConfigProvider.bind(binder, "druid.storage", S3DataSegmentMoverConfig.class); Binders.taskLogsBinder(binder).addBinding("s3").to(S3TaskLogs.class); JsonConfigProvider.bind(binder, "druid.indexer.logs", S3TaskLogsConfig.class); diff --git a/server/src/main/java/io/druid/segment/loading/OmniDataSegmentMover.java b/server/src/main/java/io/druid/segment/loading/OmniDataSegmentMover.java new file mode 100644 index 00000000000..490c936011d --- /dev/null +++ b/server/src/main/java/io/druid/segment/loading/OmniDataSegmentMover.java @@ -0,0 +1,57 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.segment.loading; + +import com.google.inject.Inject; +import com.metamx.common.MapUtils; +import io.druid.timeline.DataSegment; + +import java.util.Map; + +public class OmniDataSegmentMover implements DataSegmentMover +{ + private final Map movers; + + @Inject + public OmniDataSegmentMover( + Map movers + ) + { + this.movers = movers; + } + + @Override + public DataSegment move(DataSegment segment) throws SegmentLoadingException + { + return getMover(segment).move(segment); + } + + private DataSegmentMover getMover(DataSegment segment) throws SegmentLoadingException + { + String type = MapUtils.getString(segment.getLoadSpec(), "type"); + DataSegmentMover mover = movers.get(type); + + if (mover == null) { + throw new SegmentLoadingException("Unknown loader type[%s]. Known types are %s", type, movers.keySet()); + } + + return mover; + } +} diff --git a/services/src/main/java/io/druid/cli/CliPeon.java b/services/src/main/java/io/druid/cli/CliPeon.java index e8a5b985bd4..450c9286554 100644 --- a/services/src/main/java/io/druid/cli/CliPeon.java +++ b/services/src/main/java/io/druid/cli/CliPeon.java @@ -61,7 +61,9 @@ import io.druid.indexing.worker.executor.ExecutorLifecycle; import io.druid.indexing.worker.executor.ExecutorLifecycleConfig; import io.druid.query.QuerySegmentWalker; import io.druid.segment.loading.DataSegmentKiller; +import io.druid.segment.loading.DataSegmentMover; import io.druid.segment.loading.OmniDataSegmentKiller; +import io.druid.segment.loading.OmniDataSegmentMover; import io.druid.segment.loading.SegmentLoaderConfig; import io.druid.segment.loading.StorageLocationConfig; import io.druid.server.QueryResource; @@ -129,6 +131,8 @@ public class CliPeon extends GuiceRunnable // Build it to make it bind even if nothing binds to it. Binders.dataSegmentKillerBinder(binder); binder.bind(DataSegmentKiller.class).to(OmniDataSegmentKiller.class).in(LazySingleton.class); + Binders.dataSegmentMoverBinder(binder); + binder.bind(DataSegmentMover.class).to(OmniDataSegmentMover.class).in(LazySingleton.class); binder.bind(ExecutorLifecycle.class).in(ManageLifecycle.class); binder.bind(ExecutorLifecycleConfig.class).toInstance( From cd7a941f83752bc13e17adddaab88dcffc057b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Mon, 9 Dec 2013 14:22:39 -0800 Subject: [PATCH 100/189] consolidate path functions --- .../java/io/druid/storage/s3/S3DataSegmentKiller.java | 3 +-- .../java/io/druid/storage/s3/S3DataSegmentMover.java | 2 +- .../java/io/druid/storage/s3/S3DataSegmentPusher.java | 9 ++++++--- .../src/main/java/io/druid/storage/s3/S3Utils.java | 4 ++++ 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKiller.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKiller.java index 4e295d17aa4..0e4fde44d76 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKiller.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentKiller.java @@ -27,7 +27,6 @@ import io.druid.segment.loading.SegmentLoadingException; import io.druid.timeline.DataSegment; import org.jets3t.service.ServiceException; import org.jets3t.service.impl.rest.httpclient.RestS3Service; -import org.jets3t.service.model.S3Object; import java.util.Map; @@ -54,7 +53,7 @@ public class S3DataSegmentKiller implements DataSegmentKiller Map loadSpec = segment.getLoadSpec(); String s3Bucket = MapUtils.getString(loadSpec, "bucket"); String s3Path = MapUtils.getString(loadSpec, "key"); - String s3DescriptorPath = s3Path.substring(0, s3Path.lastIndexOf("/")) + "/descriptor.json"; + String s3DescriptorPath = S3Utils.descriptorPathForSegmentPath(s3Path); if (s3Client.isObjectInBucket(s3Bucket, s3Path)) { log.info("Removing index file[s3://%s/%s] from s3!", s3Bucket, s3Path); diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java index 245cbab8a63..599e3dce463 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java @@ -56,7 +56,7 @@ public class S3DataSegmentMover implements DataSegmentMover Map loadSpec = segment.getLoadSpec(); String s3Bucket = MapUtils.getString(loadSpec, "bucket"); String s3Path = MapUtils.getString(loadSpec, "key"); - String s3DescriptorPath = s3Path.substring(0, s3Path.lastIndexOf("/")) + "/descriptor.json"; + String s3DescriptorPath = S3Utils.descriptorPathForSegmentPath(s3Path); final String s3ArchiveBucket = config.getArchiveBucket(); diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentPusher.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentPusher.java index e8b1a99710f..a73ed4d42ac 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentPusher.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentPusher.java @@ -90,8 +90,11 @@ public class S3DataSegmentPusher implements DataSegmentPusher S3Object toPush = new S3Object(zipOutFile); final String outputBucket = config.getBucket(); + final String s3Path = outputKey + "/index.zip"; + final String s3DescriptorPath = S3Utils.descriptorPathForSegmentPath(s3Path); + toPush.setBucketName(outputBucket); - toPush.setKey(outputKey + "/index.zip"); + toPush.setKey(s3Path); if (!config.getDisableAcl()) { toPush.setAcl(AccessControlList.REST_CANNED_AUTHENTICATED_READ); } @@ -116,7 +119,7 @@ public class S3DataSegmentPusher implements DataSegmentPusher Files.copy(ByteStreams.newInputStreamSupplier(jsonMapper.writeValueAsBytes(inSegment)), descriptorFile); S3Object descriptorObject = new S3Object(descriptorFile); descriptorObject.setBucketName(outputBucket); - descriptorObject.setKey(outputKey + "/descriptor.json"); + descriptorObject.setKey(s3DescriptorPath); if (!config.getDisableAcl()) { descriptorObject.setAcl(GSAccessControlList.REST_CANNED_BUCKET_OWNER_FULL_CONTROL); } @@ -142,4 +145,4 @@ public class S3DataSegmentPusher implements DataSegmentPusher throw Throwables.propagate(e); } } -} \ No newline at end of file +} diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3Utils.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3Utils.java index 3ae7088d88f..a4764717c1d 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3Utils.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3Utils.java @@ -96,4 +96,8 @@ public class S3Utils return true; } + public static String descriptorPathForSegmentPath(String s3Path) + { + return s3Path.substring(0, s3Path.lastIndexOf("/")) + "/descriptor.json"; + } } From bb1b037f876f7419db2c646bbc01e5440818a4fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Mon, 9 Dec 2013 17:42:00 -0800 Subject: [PATCH 101/189] fix docs --- docs/content/Configuration.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/content/Configuration.md b/docs/content/Configuration.md index e5c64c74c17..13a1a67a741 100644 --- a/docs/content/Configuration.md +++ b/docs/content/Configuration.md @@ -287,8 +287,7 @@ This deep storage is used to interface with Amazon's S3. |`druid.storage.bucket`|S3 bucket name.|none| |`druid.storage.basekey`|S3 base key.|none| |`druid.storage.disableAcl`|Boolean flag for ACL.|false| -|`druid.storage.archive`|Boolean flag. Archives killed segments instead of deleting them from S3|true| -|`druid.storage.archiveBucket`|S3 bucket name to archive segments to|none| +|`druid.storage.archiveBucket`|S3 bucket name where segments get archived to when running the indexing service *archive task*|none| #### HDFS Deep Storage From 4a291fdf3094886e20d65b87554a6a5debd0a4f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Wed, 11 Dec 2013 17:25:17 -0800 Subject: [PATCH 102/189] better naming --- .../io/druid/indexing/common/actions/SegmentMoveAction.java | 2 +- .../io/druid/indexing/overlord/IndexerDBCoordinator.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentMoveAction.java b/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentMoveAction.java index 67db4fc79be..c427709c5b3 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentMoveAction.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentMoveAction.java @@ -46,7 +46,7 @@ public class SegmentMoveAction implements TaskAction throw new ISE("Segments not covered by locks for task: %s", task.getId()); } - toolbox.getIndexerDBCoordinator().moveSegments(segments); + toolbox.getIndexerDBCoordinator().updateSegmentMetadata(segments); // Emit metrics final ServiceMetricEvent.Builder metricBuilder = new ServiceMetricEvent.Builder() diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/IndexerDBCoordinator.java b/indexing-service/src/main/java/io/druid/indexing/overlord/IndexerDBCoordinator.java index 943e63fa821..f9db89b3fc9 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/IndexerDBCoordinator.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/IndexerDBCoordinator.java @@ -211,7 +211,7 @@ public class IndexerDBCoordinator return true; } - public void moveSegments(final Set segments) throws IOException + public void updateSegmentMetadata(final Set segments) throws IOException { dbi.inTransaction( new TransactionCallback() @@ -220,7 +220,7 @@ public class IndexerDBCoordinator public Void inTransaction(Handle handle, TransactionStatus transactionStatus) throws Exception { for(final DataSegment segment : segments) { - moveSegment(handle, segment); + updatePayload(handle, segment); } return null; @@ -256,7 +256,7 @@ public class IndexerDBCoordinator .execute(); } - private void moveSegment(final Handle handle, final DataSegment segment) throws IOException + private void updatePayload(final Handle handle, final DataSegment segment) throws IOException { try { handle.createStatement( From 3ae48a8191bda71e900bee767847ce56873a8c57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Wed, 11 Dec 2013 17:25:29 -0800 Subject: [PATCH 103/189] fix cut-n-paste typo --- .../src/main/java/io/druid/storage/s3/S3DataSegmentMover.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java index 599e3dce463..96a5b8a8d9e 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java @@ -34,7 +34,7 @@ import java.util.Map; public class S3DataSegmentMover implements DataSegmentMover { - private static final Logger log = new Logger(S3DataSegmentKiller.class); + private static final Logger log = new Logger(S3DataSegmentMover.class); private final RestS3Service s3Client; private final S3DataSegmentMoverConfig config; From 3af6e49cd43bd6e2231739739dfbc18d43d1bcf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Wed, 11 Dec 2013 17:29:04 -0800 Subject: [PATCH 104/189] throw exception instead of just printing a warning --- .../src/main/java/io/druid/storage/s3/S3DataSegmentMover.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java index 96a5b8a8d9e..9c79a079801 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java @@ -61,8 +61,7 @@ public class S3DataSegmentMover implements DataSegmentMover final String s3ArchiveBucket = config.getArchiveBucket(); if (s3ArchiveBucket.isEmpty()) { - log.warn("S3 archive bucket not specified, refusing to move segment [s3://%s/%s]", s3Bucket, s3Path); - return segment; + throw new SegmentLoadingException("S3 archive bucket not specified"); } if (s3Client.isObjectInBucket(s3Bucket, s3Path)) { From 123bddd6151339c6cc38e05822c221071cff8c62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Fri, 13 Dec 2013 14:29:09 -0800 Subject: [PATCH 105/189] update for new interfaces --- .../main/java/io/druid/indexing/common/task/MoveTask.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java index 4ca600f8ea5..3154d42075d 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java @@ -36,7 +36,7 @@ import org.joda.time.Interval; import java.util.List; -public class MoveTask extends AbstractTask +public class MoveTask extends AbstractFixedIntervalTask { private static final Logger log = new Logger(MoveTask.class); @@ -70,8 +70,8 @@ public class MoveTask extends AbstractTask throw new ISE("WTF?! Lock dataSource[%s] != task dataSource[%s]", myLock.getDataSource(), getDataSource()); } - if(!myLock.getInterval().equals(getImplicitLockInterval().get())) { - throw new ISE("WTF?! Lock interval[%s] != task interval[%s]", myLock.getInterval(), getImplicitLockInterval().get()); + if(!myLock.getInterval().equals(getInterval())) { + throw new ISE("WTF?! Lock interval[%s] != task interval[%s]", myLock.getInterval(), getInterval()); } // List unused segments From 6b903720021683c7d12b5befee9656fc4edea7ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Fri, 13 Dec 2013 16:40:57 -0800 Subject: [PATCH 106/189] separate segment mover and segment archiver --- pom.xml | 2 +- .../storage/s3/S3DataSegmentArchiver.java | 59 +++++++++++++++++++ ....java => S3DataSegmentArchiverConfig.java} | 2 +- .../druid/storage/s3/S3DataSegmentMover.java | 34 ++++++----- .../storage/s3/S3StorageDruidModule.java | 3 +- 5 files changed, 82 insertions(+), 18 deletions(-) create mode 100644 s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiver.java rename s3-extensions/src/main/java/io/druid/storage/s3/{S3DataSegmentMoverConfig.java => S3DataSegmentArchiverConfig.java} (96%) diff --git a/pom.xml b/pom.xml index f970d49dea5..f55d5202e2d 100644 --- a/pom.xml +++ b/pom.xml @@ -41,7 +41,7 @@ UTF-8 0.25.1 2.1.0-incubating - 0.1.6 + 0.1.7 diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiver.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiver.java new file mode 100644 index 00000000000..b339187ff29 --- /dev/null +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiver.java @@ -0,0 +1,59 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.storage.s3; + +import com.google.common.collect.ImmutableMap; +import com.google.inject.Inject; +import com.metamx.common.MapUtils; +import io.druid.segment.loading.DataSegmentArchiver; +import io.druid.segment.loading.SegmentLoadingException; +import io.druid.timeline.DataSegment; +import org.jets3t.service.impl.rest.httpclient.RestS3Service; + + +public class S3DataSegmentArchiver extends S3DataSegmentMover implements DataSegmentArchiver +{ + private final S3DataSegmentArchiverConfig config; + + @Inject + public S3DataSegmentArchiver( + RestS3Service s3Client, + S3DataSegmentArchiverConfig config + ) + { + super(s3Client); + this.config = config; + } + + @Override + public DataSegment archive(DataSegment segment) throws SegmentLoadingException + { + String targetS3Bucket = config.getArchiveBucket(); + String targetS3Path = MapUtils.getString(segment.getLoadSpec(), "key"); + + return move( + segment, + ImmutableMap.of( + "bucket", targetS3Bucket, + "key", targetS3Path + ) + ); + } +} diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMoverConfig.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiverConfig.java similarity index 96% rename from s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMoverConfig.java rename to s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiverConfig.java index a298ba9da39..53a04e43107 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMoverConfig.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiverConfig.java @@ -21,7 +21,7 @@ package io.druid.storage.s3; import com.fasterxml.jackson.annotation.JsonProperty; -public class S3DataSegmentMoverConfig +public class S3DataSegmentArchiverConfig { @JsonProperty public String archiveBucket = ""; diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java index 9c79a079801..9b3f122b590 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java @@ -37,20 +37,17 @@ public class S3DataSegmentMover implements DataSegmentMover private static final Logger log = new Logger(S3DataSegmentMover.class); private final RestS3Service s3Client; - private final S3DataSegmentMoverConfig config; @Inject public S3DataSegmentMover( - RestS3Service s3Client, - S3DataSegmentMoverConfig config + RestS3Service s3Client ) { this.s3Client = s3Client; - this.config = config; } @Override - public DataSegment move(DataSegment segment) throws SegmentLoadingException + public DataSegment move(DataSegment segment, Map targetLoadSpec) throws SegmentLoadingException { try { Map loadSpec = segment.getLoadSpec(); @@ -58,10 +55,15 @@ public class S3DataSegmentMover implements DataSegmentMover String s3Path = MapUtils.getString(loadSpec, "key"); String s3DescriptorPath = S3Utils.descriptorPathForSegmentPath(s3Path); - final String s3ArchiveBucket = config.getArchiveBucket(); + final String targetS3Bucket = MapUtils.getString(targetLoadSpec, "bucket"); + final String targetS3Path = MapUtils.getString(targetLoadSpec, "key"); + String targetS3DescriptorPath = S3Utils.descriptorPathForSegmentPath(targetS3Path); - if (s3ArchiveBucket.isEmpty()) { - throw new SegmentLoadingException("S3 archive bucket not specified"); + if (targetS3Bucket.isEmpty()) { + throw new SegmentLoadingException("Target S3 bucket is not specified"); + } + if (targetS3Path.isEmpty()) { + throw new SegmentLoadingException("Target S3 path is not specified"); } if (s3Client.isObjectInBucket(s3Bucket, s3Path)) { @@ -69,26 +71,28 @@ public class S3DataSegmentMover implements DataSegmentMover "Moving index file[s3://%s/%s] to [s3://%s/%s]", s3Bucket, s3Path, - s3ArchiveBucket, - s3Path + targetS3Bucket, + targetS3Path ); - s3Client.moveObject(s3Bucket, s3Path, s3ArchiveBucket, new S3Object(s3Path), false); + s3Client.moveObject(s3Bucket, s3Path, targetS3Bucket, new S3Object(targetS3Path), false); } if (s3Client.isObjectInBucket(s3Bucket, s3DescriptorPath)) { log.info( "Moving descriptor file[s3://%s/%s] to [s3://%s/%s]", s3Bucket, s3DescriptorPath, - s3ArchiveBucket, - s3DescriptorPath + targetS3Bucket, + targetS3DescriptorPath ); - s3Client.moveObject(s3Bucket, s3DescriptorPath, s3ArchiveBucket, new S3Object(s3DescriptorPath), false); + s3Client.moveObject(s3Bucket, s3DescriptorPath, targetS3Bucket, new S3Object(targetS3DescriptorPath), false); } return segment.withLoadSpec( ImmutableMap.builder() .putAll(loadSpec) - .put("bucket", s3ArchiveBucket).build() + .put("bucket", targetS3Bucket) + .put("key", targetS3Path) + .build() ); } catch (ServiceException e) { diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3StorageDruidModule.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3StorageDruidModule.java index fadba584bce..d30f49f976a 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3StorageDruidModule.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3StorageDruidModule.java @@ -52,9 +52,10 @@ public class S3StorageDruidModule implements DruidModule Binders.dataSegmentPullerBinder(binder).addBinding("s3_zip").to(S3DataSegmentPuller.class).in(LazySingleton.class); Binders.dataSegmentKillerBinder(binder).addBinding("s3_zip").to(S3DataSegmentKiller.class).in(LazySingleton.class); Binders.dataSegmentMoverBinder(binder).addBinding("s3_zip").to(S3DataSegmentMover.class).in(LazySingleton.class); + Binders.dataSegmentArchiverBinder(binder).addBinding("s3_zip").to(S3DataSegmentArchiver.class).in(LazySingleton.class); Binders.dataSegmentPusherBinder(binder).addBinding("s3").to(S3DataSegmentPusher.class).in(LazySingleton.class); JsonConfigProvider.bind(binder, "druid.storage", S3DataSegmentPusherConfig.class); - JsonConfigProvider.bind(binder, "druid.storage", S3DataSegmentMoverConfig.class); + JsonConfigProvider.bind(binder, "druid.storage", S3DataSegmentArchiverConfig.class); Binders.taskLogsBinder(binder).addBinding("s3").to(S3TaskLogs.class); JsonConfigProvider.bind(binder, "druid.indexer.logs", S3TaskLogsConfig.class); From ac2ca0e46cb0624ae5c010b08f89facf6e42d4e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Fri, 13 Dec 2013 17:55:31 -0800 Subject: [PATCH 107/189] separate move and archive tasks --- .../io/druid/indexing/common/TaskToolbox.java | 9 ++ .../indexing/common/TaskToolboxFactory.java | 5 + .../indexing/common/task/ArchiveTask.java | 106 ++++++++++++++++++ .../druid/indexing/common/task/MoveTask.java | 9 +- .../io/druid/indexing/common/task/Task.java | 1 + .../indexing/overlord/TaskLifecycleTest.java | 12 +- .../worker/WorkerTaskMonitorTest.java | 2 +- .../storage/s3/S3DataSegmentArchiver.java | 4 +- .../s3/S3DataSegmentArchiverConfig.java | 8 ++ .../druid/storage/s3/S3DataSegmentMover.java | 7 +- .../druid/storage/s3/S3DataSegmentPusher.java | 9 +- .../java/io/druid/storage/s3/S3Utils.java | 14 +++ .../loading/OmniDataSegmentArchiver.java | 57 ++++++++++ .../segment/loading/OmniDataSegmentMover.java | 4 +- .../src/main/java/io/druid/cli/CliPeon.java | 4 + 15 files changed, 233 insertions(+), 18 deletions(-) create mode 100644 indexing-service/src/main/java/io/druid/indexing/common/task/ArchiveTask.java create mode 100644 server/src/main/java/io/druid/segment/loading/OmniDataSegmentArchiver.java diff --git a/indexing-service/src/main/java/io/druid/indexing/common/TaskToolbox.java b/indexing-service/src/main/java/io/druid/indexing/common/TaskToolbox.java index 56f08f3c9f3..eb34f4e4c1b 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/TaskToolbox.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/TaskToolbox.java @@ -29,6 +29,7 @@ import io.druid.indexing.common.actions.TaskActionClientFactory; import io.druid.indexing.common.config.TaskConfig; import io.druid.indexing.common.task.Task; import io.druid.query.QueryRunnerFactoryConglomerate; +import io.druid.segment.loading.DataSegmentArchiver; import io.druid.segment.loading.DataSegmentKiller; import io.druid.segment.loading.DataSegmentMover; import io.druid.segment.loading.DataSegmentPusher; @@ -53,6 +54,7 @@ public class TaskToolbox private final ServiceEmitter emitter; private final DataSegmentPusher segmentPusher; private final DataSegmentKiller dataSegmentKiller; + private final DataSegmentArchiver dataSegmentArchiver; private final DataSegmentMover dataSegmentMover; private final DataSegmentAnnouncer segmentAnnouncer; private final ServerView newSegmentServerView; @@ -71,6 +73,7 @@ public class TaskToolbox DataSegmentPusher segmentPusher, DataSegmentKiller dataSegmentKiller, DataSegmentMover dataSegmentMover, + DataSegmentArchiver dataSegmentArchiver, DataSegmentAnnouncer segmentAnnouncer, ServerView newSegmentServerView, QueryRunnerFactoryConglomerate queryRunnerFactoryConglomerate, @@ -88,6 +91,7 @@ public class TaskToolbox this.segmentPusher = segmentPusher; this.dataSegmentKiller = dataSegmentKiller; this.dataSegmentMover = dataSegmentMover; + this.dataSegmentArchiver = dataSegmentArchiver; this.segmentAnnouncer = segmentAnnouncer; this.newSegmentServerView = newSegmentServerView; this.queryRunnerFactoryConglomerate = queryRunnerFactoryConglomerate; @@ -128,6 +132,11 @@ public class TaskToolbox return dataSegmentMover; } + public DataSegmentArchiver getDataSegmentArchiver() + { + return dataSegmentArchiver; + } + public DataSegmentAnnouncer getSegmentAnnouncer() { return segmentAnnouncer; diff --git a/indexing-service/src/main/java/io/druid/indexing/common/TaskToolboxFactory.java b/indexing-service/src/main/java/io/druid/indexing/common/TaskToolboxFactory.java index cf9614c4bd6..d655edc34f0 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/TaskToolboxFactory.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/TaskToolboxFactory.java @@ -29,6 +29,7 @@ import io.druid.indexing.common.actions.TaskActionClientFactory; import io.druid.indexing.common.config.TaskConfig; import io.druid.indexing.common.task.Task; import io.druid.query.QueryRunnerFactoryConglomerate; +import io.druid.segment.loading.DataSegmentArchiver; import io.druid.segment.loading.DataSegmentKiller; import io.druid.segment.loading.DataSegmentMover; import io.druid.segment.loading.DataSegmentPusher; @@ -48,6 +49,7 @@ public class TaskToolboxFactory private final DataSegmentPusher segmentPusher; private final DataSegmentKiller dataSegmentKiller; private final DataSegmentMover dataSegmentMover; + private final DataSegmentArchiver dataSegmentArchiver; private final DataSegmentAnnouncer segmentAnnouncer; private final ServerView newSegmentServerView; private final QueryRunnerFactoryConglomerate queryRunnerFactoryConglomerate; @@ -64,6 +66,7 @@ public class TaskToolboxFactory DataSegmentPusher segmentPusher, DataSegmentKiller dataSegmentKiller, DataSegmentMover dataSegmentMover, + DataSegmentArchiver dataSegmentArchiver, DataSegmentAnnouncer segmentAnnouncer, ServerView newSegmentServerView, QueryRunnerFactoryConglomerate queryRunnerFactoryConglomerate, @@ -79,6 +82,7 @@ public class TaskToolboxFactory this.segmentPusher = segmentPusher; this.dataSegmentKiller = dataSegmentKiller; this.dataSegmentMover = dataSegmentMover; + this.dataSegmentArchiver = dataSegmentArchiver; this.segmentAnnouncer = segmentAnnouncer; this.newSegmentServerView = newSegmentServerView; this.queryRunnerFactoryConglomerate = queryRunnerFactoryConglomerate; @@ -100,6 +104,7 @@ public class TaskToolboxFactory segmentPusher, dataSegmentKiller, dataSegmentMover, + dataSegmentArchiver, segmentAnnouncer, newSegmentServerView, queryRunnerFactoryConglomerate, diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/ArchiveTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/ArchiveTask.java new file mode 100644 index 00000000000..43f8fd60e18 --- /dev/null +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/ArchiveTask.java @@ -0,0 +1,106 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.indexing.common.task; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.metamx.common.ISE; +import com.metamx.common.logger.Logger; +import io.druid.indexing.common.TaskLock; +import io.druid.indexing.common.TaskStatus; +import io.druid.indexing.common.TaskToolbox; +import io.druid.indexing.common.actions.SegmentListUnusedAction; +import io.druid.indexing.common.actions.SegmentMoveAction; +import io.druid.timeline.DataSegment; +import org.joda.time.Interval; + +import java.util.List; + +public class ArchiveTask extends AbstractFixedIntervalTask +{ + private static final Logger log = new Logger(ArchiveTask.class); + + public ArchiveTask( + @JsonProperty("id") String id, + @JsonProperty("dataSource") String dataSource, + @JsonProperty("interval") Interval interval + ) + { + super(id, dataSource, interval); + } + + @Override + public String getType() + { + return "archive"; + } + + @Override + public TaskStatus run(TaskToolbox toolbox) throws Exception + { + // Confirm we have a lock (will throw if there isn't exactly one element) + final TaskLock myLock = Iterables.getOnlyElement(getTaskLocks(toolbox)); + + if (!myLock.getDataSource().equals(getDataSource())) { + throw new ISE("WTF?! Lock dataSource[%s] != task dataSource[%s]", myLock.getDataSource(), getDataSource()); + } + + if (!myLock.getInterval().equals(getInterval())) { + throw new ISE("WTF?! Lock interval[%s] != task interval[%s]", myLock.getInterval(), getInterval()); + } + + // List unused segments + final List unusedSegments = toolbox + .getTaskActionClient() + .submit(new SegmentListUnusedAction(myLock.getDataSource(), myLock.getInterval())); + + // Verify none of these segments have versions > lock version + for (final DataSegment unusedSegment : unusedSegments) { + if (unusedSegment.getVersion().compareTo(myLock.getVersion()) > 0) { + throw new ISE( + "WTF?! Unused segment[%s] has version[%s] > task version[%s]", + unusedSegment.getIdentifier(), + unusedSegment.getVersion(), + myLock.getVersion() + ); + } + + log.info("OK to archive segment: %s", unusedSegment.getIdentifier()); + } + + List archivedSegments = Lists.newLinkedList(); + + // Move segments + for (DataSegment segment : unusedSegments) { + archivedSegments.add(toolbox.getDataSegmentArchiver().archive(segment)); + } + + // Update metadata for moved segments + toolbox.getTaskActionClient().submit( + new SegmentMoveAction( + ImmutableSet.copyOf(archivedSegments) + ) + ); + + return TaskStatus.success(getId()); + } +} diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java index 3154d42075d..c8742f31c34 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java @@ -35,16 +35,20 @@ import io.druid.timeline.DataSegment; import org.joda.time.Interval; import java.util.List; +import java.util.Map; public class MoveTask extends AbstractFixedIntervalTask { private static final Logger log = new Logger(MoveTask.class); + private final Map targetLoadSpec; + @JsonCreator public MoveTask( @JsonProperty("id") String id, @JsonProperty("dataSource") String dataSource, - @JsonProperty("interval") Interval interval + @JsonProperty("interval") Interval interval, + @JsonProperty("target") Map targetLoadSpec ) { super( @@ -52,6 +56,7 @@ public class MoveTask extends AbstractFixedIntervalTask dataSource, interval ); + this.targetLoadSpec = targetLoadSpec; } @Override @@ -97,7 +102,7 @@ public class MoveTask extends AbstractFixedIntervalTask // Move segments for (DataSegment segment : unusedSegments) { - movedSegments.add(toolbox.getDataSegmentMover().move(segment)); + movedSegments.add(toolbox.getDataSegmentMover().move(segment, targetLoadSpec)); } // Update metadata for moved segments diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/Task.java b/indexing-service/src/main/java/io/druid/indexing/common/task/Task.java index 32747bdc7d3..8fa4b53bf10 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/Task.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/Task.java @@ -46,6 +46,7 @@ import io.druid.query.QueryRunner; @JsonSubTypes.Type(name = "delete", value = DeleteTask.class), @JsonSubTypes.Type(name = "kill", value = KillTask.class), @JsonSubTypes.Type(name = "move", value = MoveTask.class), + @JsonSubTypes.Type(name = "archive", value = ArchiveTask.class), @JsonSubTypes.Type(name = "index", value = IndexTask.class), @JsonSubTypes.Type(name = "index_hadoop", value = HadoopIndexTask.class), @JsonSubTypes.Type(name = "index_realtime", value = RealtimeIndexTask.class), diff --git a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java index fcbfed7589b..a873daa876f 100644 --- a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java @@ -64,6 +64,7 @@ import io.druid.indexing.overlord.config.TaskQueueConfig; import io.druid.jackson.DefaultObjectMapper; import io.druid.query.aggregation.AggregatorFactory; import io.druid.query.aggregation.DoubleSumAggregatorFactory; +import io.druid.segment.loading.DataSegmentArchiver; import io.druid.segment.loading.DataSegmentKiller; import io.druid.segment.loading.DataSegmentMover; import io.druid.segment.loading.DataSegmentPuller; @@ -89,6 +90,7 @@ import java.io.File; import java.io.IOException; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; public class TaskLifecycleTest @@ -162,11 +164,19 @@ public class TaskLifecycleTest new DataSegmentMover() { @Override - public DataSegment move(DataSegment dataSegment) throws SegmentLoadingException + public DataSegment move(DataSegment dataSegment, Map targetLoadSpec) throws SegmentLoadingException { return dataSegment; } }, + new DataSegmentArchiver() + { + @Override + public DataSegment archive(DataSegment segment) throws SegmentLoadingException + { + return segment; + } + }, null, // segment announcer null, // new segment server view null, // query runner factory conglomerate corporation unionized collective diff --git a/indexing-service/src/test/java/io/druid/indexing/worker/WorkerTaskMonitorTest.java b/indexing-service/src/test/java/io/druid/indexing/worker/WorkerTaskMonitorTest.java index 7723013d239..8d4bf32b870 100644 --- a/indexing-service/src/test/java/io/druid/indexing/worker/WorkerTaskMonitorTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/worker/WorkerTaskMonitorTest.java @@ -122,7 +122,7 @@ public class WorkerTaskMonitorTest new ThreadPoolTaskRunner( new TaskToolboxFactory( new TaskConfig(tmp.toString(), null, null, 0), - null, null, null, null, null, null, null, null, null, null, new SegmentLoaderFactory( + null, null, null, null, null, null, null, null, null, null, null, new SegmentLoaderFactory( new OmniSegmentLoader( ImmutableMap.of( "local", diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiver.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiver.java index b339187ff29..b7c04ec0c00 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiver.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiver.java @@ -46,13 +46,13 @@ public class S3DataSegmentArchiver extends S3DataSegmentMover implements DataSeg public DataSegment archive(DataSegment segment) throws SegmentLoadingException { String targetS3Bucket = config.getArchiveBucket(); - String targetS3Path = MapUtils.getString(segment.getLoadSpec(), "key"); + String targetS3BaseKey = config.getArchiveBaseKey(); return move( segment, ImmutableMap.of( "bucket", targetS3Bucket, - "key", targetS3Path + "baseKey", targetS3BaseKey ) ); } diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiverConfig.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiverConfig.java index 53a04e43107..5eb33eb1b5d 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiverConfig.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiverConfig.java @@ -26,8 +26,16 @@ public class S3DataSegmentArchiverConfig @JsonProperty public String archiveBucket = ""; + @JsonProperty + public String archiveBaseKey = ""; + public String getArchiveBucket() { return archiveBucket; } + + public String getArchiveBaseKey() + { + return archiveBaseKey; + } } diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java index 9b3f122b590..01558b5c44d 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java @@ -24,6 +24,7 @@ import com.google.inject.Inject; import com.metamx.common.MapUtils; import com.metamx.common.logger.Logger; import io.druid.segment.loading.DataSegmentMover; +import io.druid.segment.loading.DataSegmentPusherUtil; import io.druid.segment.loading.SegmentLoadingException; import io.druid.timeline.DataSegment; import org.jets3t.service.ServiceException; @@ -56,14 +57,16 @@ public class S3DataSegmentMover implements DataSegmentMover String s3DescriptorPath = S3Utils.descriptorPathForSegmentPath(s3Path); final String targetS3Bucket = MapUtils.getString(targetLoadSpec, "bucket"); - final String targetS3Path = MapUtils.getString(targetLoadSpec, "key"); + final String targetS3BaseKey = MapUtils.getString(targetLoadSpec, "baseKey"); + + final String targetS3Path = S3Utils.constructSegmentPath(targetS3BaseKey, segment); String targetS3DescriptorPath = S3Utils.descriptorPathForSegmentPath(targetS3Path); if (targetS3Bucket.isEmpty()) { throw new SegmentLoadingException("Target S3 bucket is not specified"); } if (targetS3Path.isEmpty()) { - throw new SegmentLoadingException("Target S3 path is not specified"); + throw new SegmentLoadingException("Target S3 baseKey is not specified"); } if (s3Client.isObjectInBucket(s3Bucket, s3Path)) { diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentPusher.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentPusher.java index a73ed4d42ac..664c270799b 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentPusher.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentPusher.java @@ -20,7 +20,6 @@ package io.druid.storage.s3; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Joiner; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableMap; import com.google.common.io.ByteStreams; @@ -29,7 +28,6 @@ import com.google.inject.Inject; import com.metamx.emitter.EmittingLogger; import io.druid.segment.SegmentUtils; import io.druid.segment.loading.DataSegmentPusher; -import io.druid.segment.loading.DataSegmentPusherUtil; import io.druid.timeline.DataSegment; import io.druid.utils.CompressionUtils; import org.jets3t.service.ServiceException; @@ -45,7 +43,6 @@ import java.util.concurrent.Callable; public class S3DataSegmentPusher implements DataSegmentPusher { private static final EmittingLogger log = new EmittingLogger(S3DataSegmentPusher.class); - private static final Joiner JOINER = Joiner.on("/").skipNulls(); private final RestS3Service s3Client; private final S3DataSegmentPusherConfig config; @@ -73,10 +70,7 @@ public class S3DataSegmentPusher implements DataSegmentPusher public DataSegment push(final File indexFilesDir, final DataSegment inSegment) throws IOException { log.info("Uploading [%s] to S3", indexFilesDir); - final String outputKey = JOINER.join( - config.getBaseKey().isEmpty() ? null : config.getBaseKey(), - DataSegmentPusherUtil.getStorageDir(inSegment) - ); + final String s3Path = S3Utils.constructSegmentPath(config.getBaseKey(), inSegment); final File zipOutFile = File.createTempFile("druid", "index.zip"); final long indexSize = CompressionUtils.zip(indexFilesDir, zipOutFile); @@ -90,7 +84,6 @@ public class S3DataSegmentPusher implements DataSegmentPusher S3Object toPush = new S3Object(zipOutFile); final String outputBucket = config.getBucket(); - final String s3Path = outputKey + "/index.zip"; final String s3DescriptorPath = S3Utils.descriptorPathForSegmentPath(s3Path); toPush.setBucketName(outputBucket); diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3Utils.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3Utils.java index a4764717c1d..6cf481fa2f9 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3Utils.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3Utils.java @@ -19,9 +19,12 @@ package io.druid.storage.s3; +import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.metamx.common.RetryUtils; import org.jets3t.service.ServiceException; +import io.druid.segment.loading.DataSegmentPusherUtil; +import io.druid.timeline.DataSegment; import org.jets3t.service.impl.rest.httpclient.RestS3Service; import org.jets3t.service.model.S3Bucket; import org.jets3t.service.model.S3Object; @@ -34,6 +37,8 @@ import java.util.concurrent.Callable; */ public class S3Utils { + private static final Joiner JOINER = Joiner.on("/").skipNulls(); + public static void closeStreamsQuietly(S3Object s3Obj) { if (s3Obj == null) { @@ -96,6 +101,15 @@ public class S3Utils return true; } + + public static String constructSegmentPath(String baseKey, DataSegment segment) + { + return JOINER.join( + baseKey.isEmpty() ? null : baseKey, + DataSegmentPusherUtil.getStorageDir(segment) + ) + "/index.zip"; + } + public static String descriptorPathForSegmentPath(String s3Path) { return s3Path.substring(0, s3Path.lastIndexOf("/")) + "/descriptor.json"; diff --git a/server/src/main/java/io/druid/segment/loading/OmniDataSegmentArchiver.java b/server/src/main/java/io/druid/segment/loading/OmniDataSegmentArchiver.java new file mode 100644 index 00000000000..bf34bbe17bb --- /dev/null +++ b/server/src/main/java/io/druid/segment/loading/OmniDataSegmentArchiver.java @@ -0,0 +1,57 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.segment.loading; + +import com.google.inject.Inject; +import com.metamx.common.MapUtils; +import io.druid.timeline.DataSegment; + +import java.util.Map; + +public class OmniDataSegmentArchiver implements DataSegmentArchiver +{ + private final Map archivers; + + @Inject + public OmniDataSegmentArchiver( + Map archivers + ) + { + this.archivers = archivers; + } + + @Override + public DataSegment archive(DataSegment segment) throws SegmentLoadingException + { + return getArchiver(segment).archive(segment); + } + + private DataSegmentArchiver getArchiver(DataSegment segment) throws SegmentLoadingException + { + String type = MapUtils.getString(segment.getLoadSpec(), "type"); + DataSegmentArchiver archiver = archivers.get(type); + + if (archiver == null) { + throw new SegmentLoadingException("Unknown loader type[%s]. Known types are %s", type, archivers.keySet()); + } + + return archiver; + } +} diff --git a/server/src/main/java/io/druid/segment/loading/OmniDataSegmentMover.java b/server/src/main/java/io/druid/segment/loading/OmniDataSegmentMover.java index 490c936011d..d585b0b7db9 100644 --- a/server/src/main/java/io/druid/segment/loading/OmniDataSegmentMover.java +++ b/server/src/main/java/io/druid/segment/loading/OmniDataSegmentMover.java @@ -38,9 +38,9 @@ public class OmniDataSegmentMover implements DataSegmentMover } @Override - public DataSegment move(DataSegment segment) throws SegmentLoadingException + public DataSegment move(DataSegment segment, Map targetLoadSpec) throws SegmentLoadingException { - return getMover(segment).move(segment); + return getMover(segment).move(segment, targetLoadSpec); } private DataSegmentMover getMover(DataSegment segment) throws SegmentLoadingException diff --git a/services/src/main/java/io/druid/cli/CliPeon.java b/services/src/main/java/io/druid/cli/CliPeon.java index 450c9286554..7204e5fc63a 100644 --- a/services/src/main/java/io/druid/cli/CliPeon.java +++ b/services/src/main/java/io/druid/cli/CliPeon.java @@ -60,8 +60,10 @@ import io.druid.indexing.worker.executor.ChatHandlerResource; import io.druid.indexing.worker.executor.ExecutorLifecycle; import io.druid.indexing.worker.executor.ExecutorLifecycleConfig; import io.druid.query.QuerySegmentWalker; +import io.druid.segment.loading.DataSegmentArchiver; import io.druid.segment.loading.DataSegmentKiller; import io.druid.segment.loading.DataSegmentMover; +import io.druid.segment.loading.OmniDataSegmentArchiver; import io.druid.segment.loading.OmniDataSegmentKiller; import io.druid.segment.loading.OmniDataSegmentMover; import io.druid.segment.loading.SegmentLoaderConfig; @@ -133,6 +135,8 @@ public class CliPeon extends GuiceRunnable binder.bind(DataSegmentKiller.class).to(OmniDataSegmentKiller.class).in(LazySingleton.class); Binders.dataSegmentMoverBinder(binder); binder.bind(DataSegmentMover.class).to(OmniDataSegmentMover.class).in(LazySingleton.class); + Binders.dataSegmentArchiverBinder(binder); + binder.bind(DataSegmentArchiver.class).to(OmniDataSegmentArchiver.class).in(LazySingleton.class); binder.bind(ExecutorLifecycle.class).in(ManageLifecycle.class); binder.bind(ExecutorLifecycleConfig.class).toInstance( From e333776acac38eaf5419eaba12275b9b93b9f27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Mon, 16 Dec 2013 13:22:07 -0800 Subject: [PATCH 108/189] rename SegmentMoveAction to SegmentMetadataUpdateAction --- ...mentMoveAction.java => SegmentMetadataUpdateAction.java} | 6 +++--- .../java/io/druid/indexing/common/actions/TaskAction.java | 2 +- .../java/io/druid/indexing/common/task/ArchiveTask.java | 4 ++-- .../main/java/io/druid/indexing/common/task/MoveTask.java | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) rename indexing-service/src/main/java/io/druid/indexing/common/actions/{SegmentMoveAction.java => SegmentMetadataUpdateAction.java} (92%) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentMoveAction.java b/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentMetadataUpdateAction.java similarity index 92% rename from indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentMoveAction.java rename to indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentMetadataUpdateAction.java index c427709c5b3..f996a2c6ab0 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentMoveAction.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentMetadataUpdateAction.java @@ -13,13 +13,13 @@ import io.druid.timeline.DataSegment; import java.io.IOException; import java.util.Set; -public class SegmentMoveAction implements TaskAction +public class SegmentMetadataUpdateAction implements TaskAction { @JsonIgnore private final Set segments; @JsonCreator - public SegmentMoveAction( + public SegmentMetadataUpdateAction( @JsonProperty("segments") Set segments ) { @@ -70,7 +70,7 @@ public class SegmentMoveAction implements TaskAction @Override public String toString() { - return "SegmentMoveAction{" + + return "SegmentMetadataUpdateAction{" + "segments=" + segments + '}'; } diff --git a/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskAction.java b/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskAction.java index 1d59e40c6c3..038d06be3c6 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskAction.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskAction.java @@ -36,7 +36,7 @@ import java.io.IOException; @JsonSubTypes.Type(name = "segmentListUsed", value = SegmentListUsedAction.class), @JsonSubTypes.Type(name = "segmentListUnused", value = SegmentListUnusedAction.class), @JsonSubTypes.Type(name = "segmentNuke", value = SegmentNukeAction.class), - @JsonSubTypes.Type(name = "segmentMove", value = SegmentMoveAction.class) + @JsonSubTypes.Type(name = "segmentMetadataUpdate", value = SegmentMetadataUpdateAction.class) }) public interface TaskAction { diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/ArchiveTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/ArchiveTask.java index 43f8fd60e18..97747e211ab 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/ArchiveTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/ArchiveTask.java @@ -29,7 +29,7 @@ import io.druid.indexing.common.TaskLock; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.TaskToolbox; import io.druid.indexing.common.actions.SegmentListUnusedAction; -import io.druid.indexing.common.actions.SegmentMoveAction; +import io.druid.indexing.common.actions.SegmentMetadataUpdateAction; import io.druid.timeline.DataSegment; import org.joda.time.Interval; @@ -96,7 +96,7 @@ public class ArchiveTask extends AbstractFixedIntervalTask // Update metadata for moved segments toolbox.getTaskActionClient().submit( - new SegmentMoveAction( + new SegmentMetadataUpdateAction( ImmutableSet.copyOf(archivedSegments) ) ); diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java index c8742f31c34..371f0dc38a4 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java @@ -30,7 +30,7 @@ import io.druid.indexing.common.TaskLock; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.TaskToolbox; import io.druid.indexing.common.actions.SegmentListUnusedAction; -import io.druid.indexing.common.actions.SegmentMoveAction; +import io.druid.indexing.common.actions.SegmentMetadataUpdateAction; import io.druid.timeline.DataSegment; import org.joda.time.Interval; @@ -106,7 +106,7 @@ public class MoveTask extends AbstractFixedIntervalTask } // Update metadata for moved segments - toolbox.getTaskActionClient().submit(new SegmentMoveAction( + toolbox.getTaskActionClient().submit(new SegmentMetadataUpdateAction( ImmutableSet.copyOf(movedSegments) )); From f3b8d9c047fc6a6dc6625119d1bbd088279136d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Mon, 16 Dec 2013 13:54:30 -0800 Subject: [PATCH 109/189] safely move files --- .../druid/storage/s3/S3DataSegmentMover.java | 53 ++++++++++++------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java index 01558b5c44d..fe8251a0cc0 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java @@ -69,26 +69,8 @@ public class S3DataSegmentMover implements DataSegmentMover throw new SegmentLoadingException("Target S3 baseKey is not specified"); } - if (s3Client.isObjectInBucket(s3Bucket, s3Path)) { - log.info( - "Moving index file[s3://%s/%s] to [s3://%s/%s]", - s3Bucket, - s3Path, - targetS3Bucket, - targetS3Path - ); - s3Client.moveObject(s3Bucket, s3Path, targetS3Bucket, new S3Object(targetS3Path), false); - } - if (s3Client.isObjectInBucket(s3Bucket, s3DescriptorPath)) { - log.info( - "Moving descriptor file[s3://%s/%s] to [s3://%s/%s]", - s3Bucket, - s3DescriptorPath, - targetS3Bucket, - targetS3DescriptorPath - ); - s3Client.moveObject(s3Bucket, s3DescriptorPath, targetS3Bucket, new S3Object(targetS3DescriptorPath), false); - } + safeMove(s3Bucket, s3Path, targetS3Bucket, targetS3Path); + safeMove(s3Bucket, s3DescriptorPath, targetS3Bucket, targetS3DescriptorPath); return segment.withLoadSpec( ImmutableMap.builder() @@ -102,4 +84,35 @@ public class S3DataSegmentMover implements DataSegmentMover throw new SegmentLoadingException(e, "Unable to move segment[%s]", segment.getIdentifier()); } } + + private void safeMove(String s3Bucket, String s3Path, String targetS3Bucket, String targetS3Path) + throws ServiceException, SegmentLoadingException + { + if (s3Client.isObjectInBucket(s3Bucket, s3Path)) { + log.info( + "Moving file[s3://%s/%s] to [s3://%s/%s]", + s3Bucket, + s3Path, + targetS3Bucket, + targetS3Path + ); + s3Client.moveObject(s3Bucket, s3Path, targetS3Bucket, new S3Object(targetS3Path), false); + } else { + // ensure object exists in target location + if(s3Client.isObjectInBucket(targetS3Bucket, targetS3Path)) { + log.info( + "Not moving file [s3://%s/%s], already present in target location [s3://%s/%s]", + s3Bucket, s3Path, + targetS3Bucket, targetS3Path + ); + } + else { + throw new SegmentLoadingException( + "Unable to move file [s3://%s/%s] to [s3://%s/%s], not present in either source or target location", + s3Bucket, s3Path, + targetS3Bucket, targetS3Path + ); + } + } + } } From 5ecd909f7c04527904cf3ec5b2575c3c6f69c210 Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 16 Dec 2013 14:27:01 -0800 Subject: [PATCH 110/189] reduce NPEs in CQE --- .../java/io/druid/query/ChainedExecutionQueryRunner.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/processing/src/main/java/io/druid/query/ChainedExecutionQueryRunner.java b/processing/src/main/java/io/druid/query/ChainedExecutionQueryRunner.java index 8710b627ece..316c8d8675e 100644 --- a/processing/src/main/java/io/druid/query/ChainedExecutionQueryRunner.java +++ b/processing/src/main/java/io/druid/query/ChainedExecutionQueryRunner.java @@ -84,6 +84,11 @@ public class ChainedExecutionQueryRunner implements QueryRunner { final int priority = Integer.parseInt(query.getContextValue("priority", "0")); + if (Iterables.isEmpty(queryables)) { + log.warn("No queryables found."); + return Sequences.empty(); + } + return new BaseSequence>( new BaseSequence.IteratorMaker>() { From f7f5ffc88084e7ae015bf23808c9ffade2a436c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Mon, 16 Dec 2013 15:12:15 -0800 Subject: [PATCH 111/189] add S3 segment move test and fix bug caught by test --- .../druid/storage/s3/S3DataSegmentMover.java | 16 +- .../storage/s3/S3DataSegmentMoverTest.java | 161 ++++++++++++++++++ 2 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 s3-extensions/src/test/java/io/druid/storage/s3/S3DataSegmentMoverTest.java diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java index fe8251a0cc0..a9baf1d40ab 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java @@ -19,12 +19,13 @@ package io.druid.storage.s3; +import com.google.common.base.Predicate; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; import com.google.inject.Inject; import com.metamx.common.MapUtils; import com.metamx.common.logger.Logger; import io.druid.segment.loading.DataSegmentMover; -import io.druid.segment.loading.DataSegmentPusherUtil; import io.druid.segment.loading.SegmentLoadingException; import io.druid.timeline.DataSegment; import org.jets3t.service.ServiceException; @@ -74,7 +75,18 @@ public class S3DataSegmentMover implements DataSegmentMover return segment.withLoadSpec( ImmutableMap.builder() - .putAll(loadSpec) + .putAll( + Maps.filterKeys( + loadSpec, new Predicate() + { + @Override + public boolean apply(String input) + { + return !(input.equals("bucket") || input.equals("key")); + } + } + ) + ) .put("bucket", targetS3Bucket) .put("key", targetS3Path) .build() diff --git a/s3-extensions/src/test/java/io/druid/storage/s3/S3DataSegmentMoverTest.java b/s3-extensions/src/test/java/io/druid/storage/s3/S3DataSegmentMoverTest.java new file mode 100644 index 00000000000..6206da881a4 --- /dev/null +++ b/s3-extensions/src/test/java/io/druid/storage/s3/S3DataSegmentMoverTest.java @@ -0,0 +1,161 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.storage.s3; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.metamx.common.MapUtils; +import io.druid.segment.loading.SegmentLoadingException; +import io.druid.timeline.DataSegment; +import io.druid.timeline.partition.NoneShardSpec; +import org.jets3t.service.S3ServiceException; +import org.jets3t.service.ServiceException; +import org.jets3t.service.impl.rest.httpclient.RestS3Service; +import org.jets3t.service.model.S3Object; +import org.jets3t.service.model.StorageObject; +import org.joda.time.Interval; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; +import java.util.Set; + +public class S3DataSegmentMoverTest +{ + private static final DataSegment sourceSegment = new DataSegment( + "test", + new Interval("2013-01-01/2013-01-02"), + "1", + ImmutableMap.of( + "key", + "baseKey/test/2013-01-01T00:00:00.000Z_2013-01-02T00:00:00.000Z/1/0/index.zip", + "bucket", + "main" + ), + ImmutableList.of("dim1", "dim1"), + ImmutableList.of("metric1", "metric2"), + new NoneShardSpec(), + 0, + 1 + ); + + @Test + public void testMove() throws Exception + { + MockStorageService mockS3Client = new MockStorageService(); + S3DataSegmentMover mover = new S3DataSegmentMover(mockS3Client); + + mockS3Client.putObject("main", new S3Object("baseKey/test/2013-01-01T00:00:00.000Z_2013-01-02T00:00:00.000Z/1/0/index.zip")); + mockS3Client.putObject("main", new S3Object("baseKey/test/2013-01-01T00:00:00.000Z_2013-01-02T00:00:00.000Z/1/0/descriptor.json")); + + DataSegment movedSegment = mover.move( + sourceSegment, + ImmutableMap.of("baseKey", "targetBaseKey", "bucket", "archive") + ); + + Map targetLoadSpec = movedSegment.getLoadSpec(); + Assert.assertEquals("targetBaseKey/test/2013-01-01T00:00:00.000Z_2013-01-02T00:00:00.000Z/1/0/index.zip", MapUtils.getString(targetLoadSpec, "key")); + Assert.assertEquals("archive", MapUtils.getString(targetLoadSpec, "bucket")); + Assert.assertTrue(mockS3Client.didMove()); + } + + @Test + public void testMoveNoop() throws Exception + { + MockStorageService mockS3Client = new MockStorageService(); + S3DataSegmentMover mover = new S3DataSegmentMover(mockS3Client); + + mockS3Client.putObject("archive", new S3Object("targetBaseKey/test/2013-01-01T00:00:00.000Z_2013-01-02T00:00:00.000Z/1/0/index.zip")); + mockS3Client.putObject("archive", new S3Object("targetBaseKey/test/2013-01-01T00:00:00.000Z_2013-01-02T00:00:00.000Z/1/0/descriptor.json")); + + DataSegment movedSegment = mover.move( + sourceSegment, + ImmutableMap.of("baseKey", "targetBaseKey", "bucket", "archive") + ); + + Map targetLoadSpec = movedSegment.getLoadSpec(); + + Assert.assertEquals("targetBaseKey/test/2013-01-01T00:00:00.000Z_2013-01-02T00:00:00.000Z/1/0/index.zip", MapUtils.getString(targetLoadSpec, "key")); + Assert.assertEquals("archive", MapUtils.getString(targetLoadSpec, "bucket")); + Assert.assertFalse(mockS3Client.didMove()); + } + + @Test(expected = SegmentLoadingException.class) + public void testMoveException() throws Exception + { + MockStorageService mockS3Client = new MockStorageService(); + S3DataSegmentMover mover = new S3DataSegmentMover(mockS3Client); + + mover.move( + sourceSegment, + ImmutableMap.of("baseKey", "targetBaseKey", "bucket", "archive") + ); + } + + private class MockStorageService extends RestS3Service { + Map> storage = Maps.newHashMap(); + boolean moved = false; + + private MockStorageService() throws S3ServiceException + { + super(null); + } + + public boolean didMove() { + return moved; + } + + @Override + public boolean isObjectInBucket(String bucketName, String objectKey) throws ServiceException + { + Set objects = storage.get(bucketName); + return (objects != null && objects.contains(objectKey)); + } + + @Override + public Map moveObject( + String sourceBucketName, + String sourceObjectKey, + String destinationBucketName, + StorageObject destinationObject, + boolean replaceMetadata + ) throws ServiceException + { + moved = true; + if(isObjectInBucket(sourceBucketName, sourceObjectKey)) { + this.putObject(destinationBucketName, new S3Object(destinationObject.getKey())); + storage.get(sourceBucketName).remove(sourceObjectKey); + } + return null; + } + + @Override + public S3Object putObject(String bucketName, S3Object object) throws S3ServiceException + { + if (!storage.containsKey(bucketName)) { + storage.put(bucketName, Sets.newHashSet()); + } + storage.get(bucketName).add(object.getKey()); + return object; + } + } +} From 2c7a6d26b394abfdbd5cdfb6de70fb5e5d28bdf6 Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 16 Dec 2013 15:55:28 -0800 Subject: [PATCH 112/189] fix typo in tutorial --- docs/content/Tutorial:-A-First-Look-at-Druid.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/Tutorial:-A-First-Look-at-Druid.md b/docs/content/Tutorial:-A-First-Look-at-Druid.md index e3fe41c51c6..1d5cf883267 100644 --- a/docs/content/Tutorial:-A-First-Look-at-Druid.md +++ b/docs/content/Tutorial:-A-First-Look-at-Druid.md @@ -205,7 +205,7 @@ You are probably wondering, what are these [Granularities](Granularities.html) a To issue the query and get some results, run the following in your command line: ``` -curl -X POST 'http://localhost:8083/druid/v2/?pretty' -H 'content-type: application/json' -d ````timeseries_query.body +curl -X POST 'http://localhost:8083/druid/v2/?pretty' -H 'content-type: application/json' -d @timeseries_query.body ``` Once again, you should get a JSON blob of text back with your results, that looks something like this: From f2241c8885b75e4482c234bf45ae57f58b378254 Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 16 Dec 2013 16:16:00 -0800 Subject: [PATCH 113/189] prepare for next release --- build.sh | 2 +- docs/content/Booting-a-production-cluster.md | 2 +- docs/content/Examples.md | 4 ++-- docs/content/Realtime.md | 2 +- docs/content/Tutorial:-A-First-Look-at-Druid.md | 4 ++-- docs/content/Tutorial:-Loading-Your-Data-Part-2.md | 2 +- docs/content/Tutorial:-The-Druid-Cluster.md | 6 +++--- docs/content/Tutorial:-Webstream.md | 4 ++-- docs/content/Twitter-Tutorial.textile | 2 +- examples/config/historical/runtime.properties | 2 +- examples/config/realtime/runtime.properties | 2 +- services/src/main/java/io/druid/cli/CliBroker.java | 2 +- services/src/main/java/io/druid/cli/CliCoordinator.java | 2 +- services/src/main/java/io/druid/cli/CliHadoopIndexer.java | 2 +- services/src/main/java/io/druid/cli/CliHistorical.java | 2 +- services/src/main/java/io/druid/cli/CliOverlord.java | 2 +- services/src/main/java/io/druid/cli/CliRealtime.java | 2 +- services/src/main/java/io/druid/cli/CliRealtimeExample.java | 2 +- 18 files changed, 23 insertions(+), 23 deletions(-) diff --git a/build.sh b/build.sh index e0e5f513761..b5f28547e86 100755 --- a/build.sh +++ b/build.sh @@ -30,4 +30,4 @@ echo "For examples, see: " echo " " ls -1 examples/*/*sh echo " " -echo "See also http://druid.io/docs/0.6.36" +echo "See also http://druid.io/docs/0.6.37" diff --git a/docs/content/Booting-a-production-cluster.md b/docs/content/Booting-a-production-cluster.md index db17223c8d5..c671828fd62 100644 --- a/docs/content/Booting-a-production-cluster.md +++ b/docs/content/Booting-a-production-cluster.md @@ -3,7 +3,7 @@ layout: doc_page --- # Booting a Single Node Cluster # -[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.36-bin.tar.gz). +[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.37-bin.tar.gz). The [ec2 run script](https://github.com/metamx/druid/blob/master/examples/bin/run_ec2.sh), run_ec2.sh, is located at 'examples/bin' if you have checked out the code, or at the root of the project if you've downloaded a tarball. The scripts rely on the [Amazon EC2 API Tools](http://aws.amazon.com/developertools/351), and you will need to set three environment variables: diff --git a/docs/content/Examples.md b/docs/content/Examples.md index fd18b24f1b9..90813d047b0 100644 --- a/docs/content/Examples.md +++ b/docs/content/Examples.md @@ -19,13 +19,13 @@ Clone Druid and build it: git clone https://github.com/metamx/druid.git druid cd druid git fetch --tags -git checkout druid-0.6.36 +git checkout druid-0.6.37 ./build.sh ``` ### Downloading the DSK (Druid Standalone Kit) -[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.36-bin.tar.gz) a stand-alone tarball and run it: +[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.37-bin.tar.gz) a stand-alone tarball and run it: ``` bash tar -xzf druid-services-0.X.X-bin.tar.gz diff --git a/docs/content/Realtime.md b/docs/content/Realtime.md index 71990ad57d9..87192ed88a5 100644 --- a/docs/content/Realtime.md +++ b/docs/content/Realtime.md @@ -27,7 +27,7 @@ druid.host=localhost druid.service=realtime druid.port=8083 -druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.36"] +druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.37"] druid.zk.service.host=localhost diff --git a/docs/content/Tutorial:-A-First-Look-at-Druid.md b/docs/content/Tutorial:-A-First-Look-at-Druid.md index 1d5cf883267..391302dfd69 100644 --- a/docs/content/Tutorial:-A-First-Look-at-Druid.md +++ b/docs/content/Tutorial:-A-First-Look-at-Druid.md @@ -49,7 +49,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu ### Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.36-bin.tar.gz). Download this file to a directory of your choosing. +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.37-bin.tar.gz). Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -60,7 +60,7 @@ tar -zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.36 +cd druid-services-0.6.37 ``` You should see a bunch of files: diff --git a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md index dc834ac5fd9..28448745e30 100644 --- a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md +++ b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md @@ -44,7 +44,7 @@ With real-world data, we recommend having a message bus such as [Apache Kafka](h #### Setting up Kafka -[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.36/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. +[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.37/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. Instructions for booting a Zookeeper and then Kafka cluster are available [here](http://kafka.apache.org/07/quickstart.html). diff --git a/docs/content/Tutorial:-The-Druid-Cluster.md b/docs/content/Tutorial:-The-Druid-Cluster.md index 3cba7a77582..a48bf0ebc46 100644 --- a/docs/content/Tutorial:-The-Druid-Cluster.md +++ b/docs/content/Tutorial:-The-Druid-Cluster.md @@ -13,7 +13,7 @@ In this tutorial, we will set up other types of Druid nodes as well as and exter If you followed the first tutorial, you should already have Druid downloaded. If not, let's go back and do that first. -You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.36-bin.tar.gz) +You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.37-bin.tar.gz) and untar the contents within by issuing: @@ -149,7 +149,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.36"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.37"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b @@ -238,7 +238,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.36","io.druid.extensions:druid-kafka-seven:0.6.36"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.37","io.druid.extensions:druid-kafka-seven:0.6.37"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/docs/content/Tutorial:-Webstream.md b/docs/content/Tutorial:-Webstream.md index bf201263c3a..ddc063ecb24 100644 --- a/docs/content/Tutorial:-Webstream.md +++ b/docs/content/Tutorial:-Webstream.md @@ -37,7 +37,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.36-bin.tar.gz) +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.37-bin.tar.gz) Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -48,7 +48,7 @@ tar zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.36 +cd druid-services-0.6.37 ``` You should see a bunch of files: diff --git a/docs/content/Twitter-Tutorial.textile b/docs/content/Twitter-Tutorial.textile index 5a0ef90dcf7..9b7f902ca53 100644 --- a/docs/content/Twitter-Tutorial.textile +++ b/docs/content/Twitter-Tutorial.textile @@ -9,7 +9,7 @@ There are two ways to setup Druid: download a tarball, or build it from source. h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.36-bin.tar.gz. +We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.37-bin.tar.gz. Download this bad boy to a directory of your choosing. You can extract the awesomeness within by issuing: diff --git a/examples/config/historical/runtime.properties b/examples/config/historical/runtime.properties index ce569f88f26..bd66c20d095 100644 --- a/examples/config/historical/runtime.properties +++ b/examples/config/historical/runtime.properties @@ -4,7 +4,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.36"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.37"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b diff --git a/examples/config/realtime/runtime.properties b/examples/config/realtime/runtime.properties index 9ba5d55acc4..37d6cbd53ea 100644 --- a/examples/config/realtime/runtime.properties +++ b/examples/config/realtime/runtime.properties @@ -4,7 +4,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.36","io.druid.extensions:druid-kafka-seven:0.6.36","io.druid.extensions:druid-rabbitmq:0.6.36"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.37","io.druid.extensions:druid-kafka-seven:0.6.37","io.druid.extensions:druid-rabbitmq:0.6.37"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/services/src/main/java/io/druid/cli/CliBroker.java b/services/src/main/java/io/druid/cli/CliBroker.java index b099dfa69cf..7768b8c54cb 100644 --- a/services/src/main/java/io/druid/cli/CliBroker.java +++ b/services/src/main/java/io/druid/cli/CliBroker.java @@ -53,7 +53,7 @@ import java.util.List; */ @Command( name = "broker", - description = "Runs a broker node, see http://druid.io/docs/0.6.36/Broker.html for a description" + description = "Runs a broker node, see http://druid.io/docs/0.6.37/Broker.html for a description" ) public class CliBroker extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliCoordinator.java b/services/src/main/java/io/druid/cli/CliCoordinator.java index 47240c691ee..a7ce240a011 100644 --- a/services/src/main/java/io/druid/cli/CliCoordinator.java +++ b/services/src/main/java/io/druid/cli/CliCoordinator.java @@ -60,7 +60,7 @@ import java.util.List; */ @Command( name = "coordinator", - description = "Runs the Coordinator, see http://druid.io/docs/0.6.36/Coordinator.html for a description." + description = "Runs the Coordinator, see http://druid.io/docs/0.6.37/Coordinator.html for a description." ) public class CliCoordinator extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java index e873f26c70c..5890a714eaa 100644 --- a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java +++ b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java @@ -41,7 +41,7 @@ import java.util.List; */ @Command( name = "hadoop", - description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.36/Batch-ingestion.html for a description." + description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.37/Batch-ingestion.html for a description." ) public class CliHadoopIndexer implements Runnable { diff --git a/services/src/main/java/io/druid/cli/CliHistorical.java b/services/src/main/java/io/druid/cli/CliHistorical.java index ca5449100d5..e3638a19c83 100644 --- a/services/src/main/java/io/druid/cli/CliHistorical.java +++ b/services/src/main/java/io/druid/cli/CliHistorical.java @@ -42,7 +42,7 @@ import java.util.List; */ @Command( name = "historical", - description = "Runs a Historical node, see http://druid.io/docs/0.6.36/Historical.html for a description" + description = "Runs a Historical node, see http://druid.io/docs/0.6.37/Historical.html for a description" ) public class CliHistorical extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index 2c258625e4b..44f5816fda8 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -95,7 +95,7 @@ import java.util.List; */ @Command( name = "overlord", - description = "Runs an Overlord node, see http://druid.io/docs/0.6.36/Indexing-Service.html for a description" + description = "Runs an Overlord node, see http://druid.io/docs/0.6.37/Indexing-Service.html for a description" ) public class CliOverlord extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtime.java b/services/src/main/java/io/druid/cli/CliRealtime.java index 065db03dba5..fe3d90f476a 100644 --- a/services/src/main/java/io/druid/cli/CliRealtime.java +++ b/services/src/main/java/io/druid/cli/CliRealtime.java @@ -30,7 +30,7 @@ import java.util.List; */ @Command( name = "realtime", - description = "Runs a realtime node, see http://druid.io/docs/0.6.36/Realtime.html for a description" + description = "Runs a realtime node, see http://druid.io/docs/0.6.37/Realtime.html for a description" ) public class CliRealtime extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtimeExample.java b/services/src/main/java/io/druid/cli/CliRealtimeExample.java index 819debc339a..83c9628ec49 100644 --- a/services/src/main/java/io/druid/cli/CliRealtimeExample.java +++ b/services/src/main/java/io/druid/cli/CliRealtimeExample.java @@ -42,7 +42,7 @@ import java.util.concurrent.Executor; */ @Command( name = "realtime", - description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.36/Realtime.html for a description" + description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.37/Realtime.html for a description" ) public class CliRealtimeExample extends ServerRunnable { From 7e0b4d4fd211b58a2a0d2fe507bcca0291158528 Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 16 Dec 2013 16:17:41 -0800 Subject: [PATCH 114/189] [maven-release-plugin] prepare release druid-0.6.37 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index d2c783a089d..d9b75f4ee47 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37-SNAPSHOT + 0.6.37 diff --git a/common/pom.xml b/common/pom.xml index 943aec92a08..3698e93ee65 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37-SNAPSHOT + 0.6.37 diff --git a/examples/pom.xml b/examples/pom.xml index c0019b4c9cc..9940be68bc9 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37-SNAPSHOT + 0.6.37 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 81247522e1d..1f5d6d129eb 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37-SNAPSHOT + 0.6.37 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 6a5e90cd0c6..c148c6cfda4 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37-SNAPSHOT + 0.6.37 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 07974bc9ff0..a9b39f7a79f 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37-SNAPSHOT + 0.6.37 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index ebeba81d6d5..cc360d9fb13 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37-SNAPSHOT + 0.6.37 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index c7b08ebc463..09830ac8097 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37-SNAPSHOT + 0.6.37 diff --git a/pom.xml b/pom.xml index f55d5202e2d..d236f258da6 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.37-SNAPSHOT + 0.6.37 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.37 diff --git a/processing/pom.xml b/processing/pom.xml index 9cf11cd9a6d..e51f390e9a1 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37-SNAPSHOT + 0.6.37 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 29031970b84..18dd990cd8b 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.37-SNAPSHOT + 0.6.37 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index a5b859c7ea7..022b5801ca4 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37-SNAPSHOT + 0.6.37 diff --git a/server/pom.xml b/server/pom.xml index bf05da8aec9..ffc1a0ebf8e 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37-SNAPSHOT + 0.6.37 diff --git a/services/pom.xml b/services/pom.xml index 306803a9ac7..537f7c89d28 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.37-SNAPSHOT + 0.6.37 From 48d677d1356f0d3974fecaba86535c13b708f558 Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 16 Dec 2013 16:17:45 -0800 Subject: [PATCH 115/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index d9b75f4ee47..91c20c76440 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37 + 0.6.38-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index 3698e93ee65..81fe6424308 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37 + 0.6.38-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index 9940be68bc9..ef97db4dd75 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37 + 0.6.38-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 1f5d6d129eb..47b5c0f6d11 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37 + 0.6.38-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index c148c6cfda4..64693a1ee9f 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37 + 0.6.38-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index a9b39f7a79f..34e7e27d500 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37 + 0.6.38-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index cc360d9fb13..30a863a29a1 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37 + 0.6.38-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 09830ac8097..cf14732dafe 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37 + 0.6.38-SNAPSHOT diff --git a/pom.xml b/pom.xml index d236f258da6..288cbecef8f 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.37 + 0.6.38-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.37 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index e51f390e9a1..07e60d195f8 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37 + 0.6.38-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 18dd990cd8b..60ac9636ae9 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.37 + 0.6.38-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 022b5801ca4..7183ef439e4 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37 + 0.6.38-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index ffc1a0ebf8e..028b8b3837a 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.37 + 0.6.38-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index 537f7c89d28..1f20ba42268 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.37 + 0.6.38-SNAPSHOT From 178c26f9f7db704515e544910a0a123927796791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Mon, 16 Dec 2013 16:50:31 -0800 Subject: [PATCH 116/189] fix naming and docs --- docs/content/Configuration.md | 5 +++-- .../java/io/druid/storage/s3/S3DataSegmentArchiver.java | 3 +-- .../io/druid/storage/s3/S3DataSegmentArchiverConfig.java | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/content/Configuration.md b/docs/content/Configuration.md index 13a1a67a741..e317ac0ecd4 100644 --- a/docs/content/Configuration.md +++ b/docs/content/Configuration.md @@ -285,9 +285,10 @@ This deep storage is used to interface with Amazon's S3. |Property|Description|Default| |--------|-----------|-------| |`druid.storage.bucket`|S3 bucket name.|none| -|`druid.storage.basekey`|S3 base key.|none| +|`druid.storage.basekey`|S3 object key prefix for storage.|none| |`druid.storage.disableAcl`|Boolean flag for ACL.|false| -|`druid.storage.archiveBucket`|S3 bucket name where segments get archived to when running the indexing service *archive task*|none| +|`druid.storage.archiveBucket`|S3 bucket name for archiving when running the indexing-service *archive task*.|none| +|`druid.storage.archiveBasekey`|S3 object key prefix for archiving.|none| #### HDFS Deep Storage diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiver.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiver.java index b7c04ec0c00..1c642f110bd 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiver.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiver.java @@ -21,7 +21,6 @@ package io.druid.storage.s3; import com.google.common.collect.ImmutableMap; import com.google.inject.Inject; -import com.metamx.common.MapUtils; import io.druid.segment.loading.DataSegmentArchiver; import io.druid.segment.loading.SegmentLoadingException; import io.druid.timeline.DataSegment; @@ -46,7 +45,7 @@ public class S3DataSegmentArchiver extends S3DataSegmentMover implements DataSeg public DataSegment archive(DataSegment segment) throws SegmentLoadingException { String targetS3Bucket = config.getArchiveBucket(); - String targetS3BaseKey = config.getArchiveBaseKey(); + String targetS3BaseKey = config.getArchiveBasekey(); return move( segment, diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiverConfig.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiverConfig.java index 5eb33eb1b5d..9aeccb74afe 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiverConfig.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiverConfig.java @@ -27,15 +27,15 @@ public class S3DataSegmentArchiverConfig public String archiveBucket = ""; @JsonProperty - public String archiveBaseKey = ""; + public String archiveBasekey = ""; public String getArchiveBucket() { return archiveBucket; } - public String getArchiveBaseKey() + public String getArchiveBasekey() { - return archiveBaseKey; + return archiveBasekey; } } From 3b2833d55dcc429ae3547c2eb5f6bc05459f98f9 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Mon, 16 Dec 2013 21:05:14 -0800 Subject: [PATCH 117/189] JavaScriptAggregatorFactory: Handle missing columns by passing down null args --- .../JavaScriptAggregatorFactory.java | 8 +++-- .../aggregation/JavaScriptAggregatorTest.java | 34 +++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/processing/src/main/java/io/druid/query/aggregation/JavaScriptAggregatorFactory.java b/processing/src/main/java/io/druid/query/aggregation/JavaScriptAggregatorFactory.java index ff55c9cee7f..927ab89676f 100644 --- a/processing/src/main/java/io/druid/query/aggregation/JavaScriptAggregatorFactory.java +++ b/processing/src/main/java/io/druid/query/aggregation/JavaScriptAggregatorFactory.java @@ -265,9 +265,11 @@ public class JavaScriptAggregatorFactory implements AggregatorFactory final Object[] args = new Object[size + 1]; args[0] = current; - int i = 0; - while (i < size) { - args[i + 1] = selectorList[i++].get(); + for (int i = 0 ; i < size ; i++) { + final ObjectColumnSelector selector = selectorList[i]; + if (selector != null) { + args[i + 1] = selector.get(); + } } final Object res = fnAggregate.call(cx, scope, scope, args); diff --git a/processing/src/test/java/io/druid/query/aggregation/JavaScriptAggregatorTest.java b/processing/src/test/java/io/druid/query/aggregation/JavaScriptAggregatorTest.java index 2435211dfe9..7f087559339 100644 --- a/processing/src/test/java/io/druid/query/aggregation/JavaScriptAggregatorTest.java +++ b/processing/src/test/java/io/druid/query/aggregation/JavaScriptAggregatorTest.java @@ -27,6 +27,7 @@ import org.junit.Test; import java.nio.ByteBuffer; import java.util.Arrays; +import java.util.Collections; import java.util.Map; public class JavaScriptAggregatorTest @@ -141,6 +142,39 @@ public class JavaScriptAggregatorTest Assert.assertEquals(val, agg.get(buf, position)); } + @Test + public void testAggregateMissingColumn() + { + Map script = scriptDoubleSum; + + JavaScriptAggregator agg = new JavaScriptAggregator( + "billy", + Collections.singletonList(null), + JavaScriptAggregatorFactory.compileScript(script.get("fnAggregate"), + script.get("fnReset"), + script.get("fnCombine")) + ); + + final double val = 0; + + Assert.assertEquals("billy", agg.getName()); + + agg.reset(); + Assert.assertEquals(val, agg.get()); + Assert.assertEquals(val, agg.get()); + Assert.assertEquals(val, agg.get()); + + agg.aggregate(); + Assert.assertEquals(val, agg.get()); + Assert.assertEquals(val, agg.get()); + Assert.assertEquals(val, agg.get()); + + agg.aggregate(); + Assert.assertEquals(val, agg.get()); + Assert.assertEquals(val, agg.get()); + Assert.assertEquals(val, agg.get()); + } + public static void main(String... args) throws Exception { final LoopingFloatColumnSelector selector = new LoopingFloatColumnSelector(new float[]{42.12f, 9f}); From af202d7576f626561c22d50f6611e2885630f7f3 Mon Sep 17 00:00:00 2001 From: Hagen Rother Date: Tue, 17 Dec 2013 15:27:17 +0100 Subject: [PATCH 118/189] improve kafka intake stability --- .../firehose/kafka/KafkaEightFirehoseFactory.java | 10 +++++++++- .../firehose/kafka/KafkaSevenFirehoseFactory.java | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/kafka-eight/src/main/java/io/druid/firehose/kafka/KafkaEightFirehoseFactory.java b/kafka-eight/src/main/java/io/druid/firehose/kafka/KafkaEightFirehoseFactory.java index c8bf876cc92..86c165c04d8 100644 --- a/kafka-eight/src/main/java/io/druid/firehose/kafka/KafkaEightFirehoseFactory.java +++ b/kafka-eight/src/main/java/io/druid/firehose/kafka/KafkaEightFirehoseFactory.java @@ -107,7 +107,15 @@ public class KafkaEightFirehoseFactory implements FirehoseFactory return null; } - return parser.parse(ByteBuffer.wrap(message)); + try { + return parser.parse(ByteBuffer.wrap(message)); + } + catch (Exception e) { + throw new FormattedException.Builder() + .withErrorCode(FormattedException.ErrorCode.UNPARSABLE_ROW) + .withMessage(String.format("Error parsing[%s], got [%s]", ByteBuffer.wrap(message), e.toString())) + .build(); + } } @Override diff --git a/kafka-seven/src/main/java/io/druid/firehose/kafka/KafkaSevenFirehoseFactory.java b/kafka-seven/src/main/java/io/druid/firehose/kafka/KafkaSevenFirehoseFactory.java index 8f200d1cdbc..c227b323877 100644 --- a/kafka-seven/src/main/java/io/druid/firehose/kafka/KafkaSevenFirehoseFactory.java +++ b/kafka-seven/src/main/java/io/druid/firehose/kafka/KafkaSevenFirehoseFactory.java @@ -120,7 +120,15 @@ public class KafkaSevenFirehoseFactory implements FirehoseFactory public InputRow parseMessage(Message message) throws FormattedException { - return parser.parse(message.payload()); + try { + return parser.parse(message.payload()); + } + catch (Exception e) { + throw new FormattedException.Builder() + .withErrorCode(FormattedException.ErrorCode.UNPARSABLE_ROW) + .withMessage(String.format("Error parsing[%s], got [%s]", message.payload(), e.toString())) + .build(); + } } @Override From 58d1262edff4b513f824d31d6e3792ace2cf083e Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Tue, 17 Dec 2013 08:16:49 -0800 Subject: [PATCH 119/189] Indexing console: Clarify "Complete" with "recently completed" --- indexing-service/src/main/resources/indexer_static/console.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexing-service/src/main/resources/indexer_static/console.html b/indexing-service/src/main/resources/indexer_static/console.html index f51383c72c0..e8221aa287e 100644 --- a/indexing-service/src/main/resources/indexer_static/console.html +++ b/indexing-service/src/main/resources/indexer_static/console.html @@ -51,7 +51,7 @@
Loading Waiting Tasks... this may take a few minutes
-

Complete Tasks

+

Complete Tasks - Tasks recently completed

Loading Complete Tasks... this may take a few minutes
From bbb2754a7c3a36123da49b79c86ae5abdd3f8a4c Mon Sep 17 00:00:00 2001 From: fjy Date: Tue, 17 Dec 2013 16:22:36 -0800 Subject: [PATCH 120/189] fix redirects in druid --- .../main/java/io/druid/cli/CliOverlord.java | 26 ++++++++--------- .../CoordinatorJettyServerInitializer.java | 29 +++++++++---------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index 44f5816fda8..9ae0fd99227 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -79,9 +79,7 @@ import io.druid.tasklogs.TaskLogStreamer; import io.druid.tasklogs.TaskLogs; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.HandlerList; -import org.eclipse.jetty.server.handler.ResourceHandler; import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; @@ -238,12 +236,12 @@ public class CliOverlord extends ServerRunnable @Override public void initialize(Server server, Injector injector) { - final ServletContextHandler redirect = new ServletContextHandler(ServletContextHandler.SESSIONS); - redirect.setContextPath("/"); - redirect.addFilter(new FilterHolder(injector.getInstance(RedirectFilter.class)), "/*", null); + final ServletContextHandler root = new ServletContextHandler(ServletContextHandler.SESSIONS); - final ResourceHandler resourceHandler = new ResourceHandler(); - resourceHandler.setBaseResource( + ServletHolder holderPwd = new ServletHolder("default", DefaultServlet.class); + + root.addServlet(holderPwd, "/"); + root.setBaseResource( new ResourceCollection( new String[]{ TaskMaster.class.getClassLoader().getResource("static").toExternalForm(), @@ -251,17 +249,17 @@ public class CliOverlord extends ServerRunnable } ) ); + //root.setResourceBase(DruidCoordinator.class.getClassLoader().getResource("static").toExternalForm()); + root.addFilter(new FilterHolder(injector.getInstance(RedirectFilter.class)), "/*", null); + root.addFilter(GzipFilter.class, "/*", null); - final ServletContextHandler root = new ServletContextHandler(ServletContextHandler.SESSIONS); - root.setContextPath("/"); + // Can't use /* here because of Guice and Jetty static content conflicts + root.addFilter(GuiceFilter.class, "/druid/*", null); HandlerList handlerList = new HandlerList(); - handlerList.setHandlers(new Handler[]{redirect, resourceHandler, root, new DefaultHandler()}); - server.setHandler(handlerList); + handlerList.setHandlers(new Handler[]{root}); - root.addServlet(new ServletHolder(new DefaultServlet()), "/*"); - root.addFilter(GzipFilter.class, "/*", null); - root.addFilter(GuiceFilter.class, "/*", null); + server.setHandler(handlerList); } } } diff --git a/services/src/main/java/io/druid/cli/CoordinatorJettyServerInitializer.java b/services/src/main/java/io/druid/cli/CoordinatorJettyServerInitializer.java index 12cf906b3cc..2ba09f24717 100644 --- a/services/src/main/java/io/druid/cli/CoordinatorJettyServerInitializer.java +++ b/services/src/main/java/io/druid/cli/CoordinatorJettyServerInitializer.java @@ -26,9 +26,7 @@ import io.druid.server.http.RedirectFilter; import io.druid.server.initialization.JettyServerInitializer; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.HandlerList; -import org.eclipse.jetty.server.handler.ResourceHandler; import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; @@ -42,22 +40,23 @@ class CoordinatorJettyServerInitializer implements JettyServerInitializer @Override public void initialize(Server server, Injector injector) { - final ServletContextHandler redirect = new ServletContextHandler(ServletContextHandler.SESSIONS); - redirect.setContextPath("/"); - redirect.addFilter(new FilterHolder(injector.getInstance(RedirectFilter.class)), "/*", null); - - final ResourceHandler resourceHandler = new ResourceHandler(); - resourceHandler.setResourceBase(DruidCoordinator.class.getClassLoader().getResource("static").toExternalForm()); - final ServletContextHandler root = new ServletContextHandler(ServletContextHandler.SESSIONS); - root.setContextPath("/"); + + ServletHolder holderPwd = new ServletHolder("default", DefaultServlet.class); + + root.addServlet(holderPwd, "/"); + root.setResourceBase(DruidCoordinator.class.getClassLoader().getResource("static").toExternalForm()); + root.addFilter(new FilterHolder(injector.getInstance(RedirectFilter.class)), "/*", null); + root.addFilter(GzipFilter.class, "/*", null); + + // Can't use '/*' here because of Guice and Jetty static content conflicts + // The coordinator really needs a standarized api path + root.addFilter(GuiceFilter.class, "/info/*", null); + root.addFilter(GuiceFilter.class, "/coordinator/*", null); HandlerList handlerList = new HandlerList(); - handlerList.setHandlers(new Handler[]{redirect, resourceHandler, root, new DefaultHandler()}); - server.setHandler(handlerList); + handlerList.setHandlers(new Handler[]{root}); - root.addServlet(new ServletHolder(new DefaultServlet()), "/*"); - root.addFilter(GzipFilter.class, "/*", null); - root.addFilter(GuiceFilter.class, "/*", null); + server.setHandler(handlerList); } } From 69f33b0acd225cc1a31c6a80e0e42293ba26eb5d Mon Sep 17 00:00:00 2001 From: fjy Date: Tue, 17 Dec 2013 16:24:15 -0800 Subject: [PATCH 121/189] remove commented line --- services/src/main/java/io/druid/cli/CliOverlord.java | 1 - 1 file changed, 1 deletion(-) diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index 9ae0fd99227..6b6123655cf 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -249,7 +249,6 @@ public class CliOverlord extends ServerRunnable } ) ); - //root.setResourceBase(DruidCoordinator.class.getClassLoader().getResource("static").toExternalForm()); root.addFilter(new FilterHolder(injector.getInstance(RedirectFilter.class)), "/*", null); root.addFilter(GzipFilter.class, "/*", null); From 07875dd30bfca7572f8b110499da0604c7d129d8 Mon Sep 17 00:00:00 2001 From: fjy Date: Tue, 17 Dec 2013 16:28:11 -0800 Subject: [PATCH 122/189] prepare for next release --- build.sh | 2 +- docs/content/Booting-a-production-cluster.md | 2 +- docs/content/Examples.md | 4 ++-- docs/content/Realtime.md | 2 +- docs/content/Tutorial:-A-First-Look-at-Druid.md | 4 ++-- docs/content/Tutorial:-Loading-Your-Data-Part-2.md | 2 +- docs/content/Tutorial:-The-Druid-Cluster.md | 6 +++--- docs/content/Tutorial:-Webstream.md | 4 ++-- docs/content/Twitter-Tutorial.textile | 2 +- examples/config/historical/runtime.properties | 2 +- examples/config/realtime/runtime.properties | 2 +- services/src/main/java/io/druid/cli/CliBroker.java | 2 +- services/src/main/java/io/druid/cli/CliCoordinator.java | 2 +- services/src/main/java/io/druid/cli/CliHadoopIndexer.java | 2 +- services/src/main/java/io/druid/cli/CliHistorical.java | 2 +- services/src/main/java/io/druid/cli/CliOverlord.java | 2 +- services/src/main/java/io/druid/cli/CliRealtime.java | 2 +- services/src/main/java/io/druid/cli/CliRealtimeExample.java | 2 +- 18 files changed, 23 insertions(+), 23 deletions(-) diff --git a/build.sh b/build.sh index b5f28547e86..152b403ce2c 100755 --- a/build.sh +++ b/build.sh @@ -30,4 +30,4 @@ echo "For examples, see: " echo " " ls -1 examples/*/*sh echo " " -echo "See also http://druid.io/docs/0.6.37" +echo "See also http://druid.io/docs/0.6.38" diff --git a/docs/content/Booting-a-production-cluster.md b/docs/content/Booting-a-production-cluster.md index c671828fd62..478e1f409f0 100644 --- a/docs/content/Booting-a-production-cluster.md +++ b/docs/content/Booting-a-production-cluster.md @@ -3,7 +3,7 @@ layout: doc_page --- # Booting a Single Node Cluster # -[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.37-bin.tar.gz). +[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.38-bin.tar.gz). The [ec2 run script](https://github.com/metamx/druid/blob/master/examples/bin/run_ec2.sh), run_ec2.sh, is located at 'examples/bin' if you have checked out the code, or at the root of the project if you've downloaded a tarball. The scripts rely on the [Amazon EC2 API Tools](http://aws.amazon.com/developertools/351), and you will need to set three environment variables: diff --git a/docs/content/Examples.md b/docs/content/Examples.md index 90813d047b0..528f15876ad 100644 --- a/docs/content/Examples.md +++ b/docs/content/Examples.md @@ -19,13 +19,13 @@ Clone Druid and build it: git clone https://github.com/metamx/druid.git druid cd druid git fetch --tags -git checkout druid-0.6.37 +git checkout druid-0.6.38 ./build.sh ``` ### Downloading the DSK (Druid Standalone Kit) -[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.37-bin.tar.gz) a stand-alone tarball and run it: +[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.38-bin.tar.gz) a stand-alone tarball and run it: ``` bash tar -xzf druid-services-0.X.X-bin.tar.gz diff --git a/docs/content/Realtime.md b/docs/content/Realtime.md index 87192ed88a5..c604a1405c3 100644 --- a/docs/content/Realtime.md +++ b/docs/content/Realtime.md @@ -27,7 +27,7 @@ druid.host=localhost druid.service=realtime druid.port=8083 -druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.37"] +druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.38"] druid.zk.service.host=localhost diff --git a/docs/content/Tutorial:-A-First-Look-at-Druid.md b/docs/content/Tutorial:-A-First-Look-at-Druid.md index 391302dfd69..d20d4e0ec83 100644 --- a/docs/content/Tutorial:-A-First-Look-at-Druid.md +++ b/docs/content/Tutorial:-A-First-Look-at-Druid.md @@ -49,7 +49,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu ### Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.37-bin.tar.gz). Download this file to a directory of your choosing. +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.38-bin.tar.gz). Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -60,7 +60,7 @@ tar -zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.37 +cd druid-services-0.6.38 ``` You should see a bunch of files: diff --git a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md index 28448745e30..cd6df42185c 100644 --- a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md +++ b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md @@ -44,7 +44,7 @@ With real-world data, we recommend having a message bus such as [Apache Kafka](h #### Setting up Kafka -[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.37/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. +[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.38/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. Instructions for booting a Zookeeper and then Kafka cluster are available [here](http://kafka.apache.org/07/quickstart.html). diff --git a/docs/content/Tutorial:-The-Druid-Cluster.md b/docs/content/Tutorial:-The-Druid-Cluster.md index a48bf0ebc46..c8c7c8e4b93 100644 --- a/docs/content/Tutorial:-The-Druid-Cluster.md +++ b/docs/content/Tutorial:-The-Druid-Cluster.md @@ -13,7 +13,7 @@ In this tutorial, we will set up other types of Druid nodes as well as and exter If you followed the first tutorial, you should already have Druid downloaded. If not, let's go back and do that first. -You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.37-bin.tar.gz) +You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.38-bin.tar.gz) and untar the contents within by issuing: @@ -149,7 +149,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.37"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.38"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b @@ -238,7 +238,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.37","io.druid.extensions:druid-kafka-seven:0.6.37"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.38","io.druid.extensions:druid-kafka-seven:0.6.38"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/docs/content/Tutorial:-Webstream.md b/docs/content/Tutorial:-Webstream.md index ddc063ecb24..8d5e75a0da8 100644 --- a/docs/content/Tutorial:-Webstream.md +++ b/docs/content/Tutorial:-Webstream.md @@ -37,7 +37,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.37-bin.tar.gz) +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.38-bin.tar.gz) Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -48,7 +48,7 @@ tar zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.37 +cd druid-services-0.6.38 ``` You should see a bunch of files: diff --git a/docs/content/Twitter-Tutorial.textile b/docs/content/Twitter-Tutorial.textile index 9b7f902ca53..ae36ab176b4 100644 --- a/docs/content/Twitter-Tutorial.textile +++ b/docs/content/Twitter-Tutorial.textile @@ -9,7 +9,7 @@ There are two ways to setup Druid: download a tarball, or build it from source. h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.37-bin.tar.gz. +We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.38-bin.tar.gz. Download this bad boy to a directory of your choosing. You can extract the awesomeness within by issuing: diff --git a/examples/config/historical/runtime.properties b/examples/config/historical/runtime.properties index bd66c20d095..ae40823c151 100644 --- a/examples/config/historical/runtime.properties +++ b/examples/config/historical/runtime.properties @@ -4,7 +4,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.37"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.38"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b diff --git a/examples/config/realtime/runtime.properties b/examples/config/realtime/runtime.properties index 37d6cbd53ea..b92d608ed58 100644 --- a/examples/config/realtime/runtime.properties +++ b/examples/config/realtime/runtime.properties @@ -4,7 +4,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.37","io.druid.extensions:druid-kafka-seven:0.6.37","io.druid.extensions:druid-rabbitmq:0.6.37"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.38","io.druid.extensions:druid-kafka-seven:0.6.38","io.druid.extensions:druid-rabbitmq:0.6.38"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/services/src/main/java/io/druid/cli/CliBroker.java b/services/src/main/java/io/druid/cli/CliBroker.java index 7768b8c54cb..c69eba26445 100644 --- a/services/src/main/java/io/druid/cli/CliBroker.java +++ b/services/src/main/java/io/druid/cli/CliBroker.java @@ -53,7 +53,7 @@ import java.util.List; */ @Command( name = "broker", - description = "Runs a broker node, see http://druid.io/docs/0.6.37/Broker.html for a description" + description = "Runs a broker node, see http://druid.io/docs/0.6.38/Broker.html for a description" ) public class CliBroker extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliCoordinator.java b/services/src/main/java/io/druid/cli/CliCoordinator.java index a7ce240a011..de50d2c1042 100644 --- a/services/src/main/java/io/druid/cli/CliCoordinator.java +++ b/services/src/main/java/io/druid/cli/CliCoordinator.java @@ -60,7 +60,7 @@ import java.util.List; */ @Command( name = "coordinator", - description = "Runs the Coordinator, see http://druid.io/docs/0.6.37/Coordinator.html for a description." + description = "Runs the Coordinator, see http://druid.io/docs/0.6.38/Coordinator.html for a description." ) public class CliCoordinator extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java index 5890a714eaa..4f4bdd1336a 100644 --- a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java +++ b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java @@ -41,7 +41,7 @@ import java.util.List; */ @Command( name = "hadoop", - description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.37/Batch-ingestion.html for a description." + description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.38/Batch-ingestion.html for a description." ) public class CliHadoopIndexer implements Runnable { diff --git a/services/src/main/java/io/druid/cli/CliHistorical.java b/services/src/main/java/io/druid/cli/CliHistorical.java index e3638a19c83..746eb88113a 100644 --- a/services/src/main/java/io/druid/cli/CliHistorical.java +++ b/services/src/main/java/io/druid/cli/CliHistorical.java @@ -42,7 +42,7 @@ import java.util.List; */ @Command( name = "historical", - description = "Runs a Historical node, see http://druid.io/docs/0.6.37/Historical.html for a description" + description = "Runs a Historical node, see http://druid.io/docs/0.6.38/Historical.html for a description" ) public class CliHistorical extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index 6b6123655cf..077654a335f 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -93,7 +93,7 @@ import java.util.List; */ @Command( name = "overlord", - description = "Runs an Overlord node, see http://druid.io/docs/0.6.37/Indexing-Service.html for a description" + description = "Runs an Overlord node, see http://druid.io/docs/0.6.38/Indexing-Service.html for a description" ) public class CliOverlord extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtime.java b/services/src/main/java/io/druid/cli/CliRealtime.java index fe3d90f476a..d414cf673f4 100644 --- a/services/src/main/java/io/druid/cli/CliRealtime.java +++ b/services/src/main/java/io/druid/cli/CliRealtime.java @@ -30,7 +30,7 @@ import java.util.List; */ @Command( name = "realtime", - description = "Runs a realtime node, see http://druid.io/docs/0.6.37/Realtime.html for a description" + description = "Runs a realtime node, see http://druid.io/docs/0.6.38/Realtime.html for a description" ) public class CliRealtime extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtimeExample.java b/services/src/main/java/io/druid/cli/CliRealtimeExample.java index 83c9628ec49..66021e22c66 100644 --- a/services/src/main/java/io/druid/cli/CliRealtimeExample.java +++ b/services/src/main/java/io/druid/cli/CliRealtimeExample.java @@ -42,7 +42,7 @@ import java.util.concurrent.Executor; */ @Command( name = "realtime", - description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.37/Realtime.html for a description" + description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.38/Realtime.html for a description" ) public class CliRealtimeExample extends ServerRunnable { From ed9f4b4cf2f738657c9024a306d6f76d25ee3a4a Mon Sep 17 00:00:00 2001 From: fjy Date: Tue, 17 Dec 2013 16:29:49 -0800 Subject: [PATCH 123/189] [maven-release-plugin] prepare release druid-0.6.38 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 91c20c76440..534eaab7b2f 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38-SNAPSHOT + 0.6.38 diff --git a/common/pom.xml b/common/pom.xml index 81fe6424308..60f25e603d8 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38-SNAPSHOT + 0.6.38 diff --git a/examples/pom.xml b/examples/pom.xml index ef97db4dd75..3fdafea8faa 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38-SNAPSHOT + 0.6.38 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 47b5c0f6d11..416b70827e8 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38-SNAPSHOT + 0.6.38 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 64693a1ee9f..bea37023db5 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38-SNAPSHOT + 0.6.38 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 34e7e27d500..b29a2e70f9a 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38-SNAPSHOT + 0.6.38 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 30a863a29a1..813cae3a6e4 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38-SNAPSHOT + 0.6.38 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index cf14732dafe..86374e5883e 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38-SNAPSHOT + 0.6.38 diff --git a/pom.xml b/pom.xml index 288cbecef8f..539971c9099 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.38-SNAPSHOT + 0.6.38 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.38 diff --git a/processing/pom.xml b/processing/pom.xml index 07e60d195f8..efe264fab49 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38-SNAPSHOT + 0.6.38 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 60ac9636ae9..8fa443b6c18 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.38-SNAPSHOT + 0.6.38 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 7183ef439e4..2aea865aecb 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38-SNAPSHOT + 0.6.38 diff --git a/server/pom.xml b/server/pom.xml index 028b8b3837a..222f63bf8b3 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38-SNAPSHOT + 0.6.38 diff --git a/services/pom.xml b/services/pom.xml index 1f20ba42268..9b36b8279ab 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.38-SNAPSHOT + 0.6.38 From 5932150d24ed8c565f9b4f9c4e13d89dd974fb48 Mon Sep 17 00:00:00 2001 From: fjy Date: Tue, 17 Dec 2013 16:29:53 -0800 Subject: [PATCH 124/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 534eaab7b2f..a558bc27751 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38 + 0.6.39-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index 60f25e603d8..c5e1a28fd2a 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38 + 0.6.39-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index 3fdafea8faa..62ac9caa341 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38 + 0.6.39-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 416b70827e8..cc85881c688 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38 + 0.6.39-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index bea37023db5..9c1800a508f 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38 + 0.6.39-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index b29a2e70f9a..4c94d67e16d 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38 + 0.6.39-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 813cae3a6e4..dab6df1961c 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38 + 0.6.39-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 86374e5883e..b118d103e36 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38 + 0.6.39-SNAPSHOT diff --git a/pom.xml b/pom.xml index 539971c9099..526caaf9e8a 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.38 + 0.6.39-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.38 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index efe264fab49..f515f1f4289 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38 + 0.6.39-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 8fa443b6c18..8cf219ea40e 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.38 + 0.6.39-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 2aea865aecb..92562336cfc 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38 + 0.6.39-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index 222f63bf8b3..c1c4ae6e806 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.38 + 0.6.39-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index 9b36b8279ab..3b20b66d8b1 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.38 + 0.6.39-SNAPSHOT From 0051877f8486d06f05c46ed96400da7c89442d61 Mon Sep 17 00:00:00 2001 From: fjy Date: Tue, 17 Dec 2013 17:46:16 -0800 Subject: [PATCH 125/189] fix status endpoint --- services/src/main/java/io/druid/cli/CliOverlord.java | 1 + .../java/io/druid/cli/CoordinatorJettyServerInitializer.java | 1 + 2 files changed, 2 insertions(+) diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index 077654a335f..feb29bccfcd 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -253,6 +253,7 @@ public class CliOverlord extends ServerRunnable root.addFilter(GzipFilter.class, "/*", null); // Can't use /* here because of Guice and Jetty static content conflicts + root.addFilter(GuiceFilter.class, "/status/*", null); root.addFilter(GuiceFilter.class, "/druid/*", null); HandlerList handlerList = new HandlerList(); diff --git a/services/src/main/java/io/druid/cli/CoordinatorJettyServerInitializer.java b/services/src/main/java/io/druid/cli/CoordinatorJettyServerInitializer.java index 2ba09f24717..be7a59ea2a8 100644 --- a/services/src/main/java/io/druid/cli/CoordinatorJettyServerInitializer.java +++ b/services/src/main/java/io/druid/cli/CoordinatorJettyServerInitializer.java @@ -51,6 +51,7 @@ class CoordinatorJettyServerInitializer implements JettyServerInitializer // Can't use '/*' here because of Guice and Jetty static content conflicts // The coordinator really needs a standarized api path + root.addFilter(GuiceFilter.class, "/status/*", null); root.addFilter(GuiceFilter.class, "/info/*", null); root.addFilter(GuiceFilter.class, "/coordinator/*", null); From d112b2d2111288a75de303dbb50c29e9feab0eda Mon Sep 17 00:00:00 2001 From: fjy Date: Tue, 17 Dec 2013 17:47:04 -0800 Subject: [PATCH 126/189] prepare for next release --- build.sh | 2 +- docs/content/Booting-a-production-cluster.md | 2 +- docs/content/Examples.md | 4 ++-- docs/content/Realtime.md | 2 +- docs/content/Tutorial:-A-First-Look-at-Druid.md | 4 ++-- docs/content/Tutorial:-Loading-Your-Data-Part-2.md | 2 +- docs/content/Tutorial:-The-Druid-Cluster.md | 6 +++--- docs/content/Tutorial:-Webstream.md | 4 ++-- docs/content/Twitter-Tutorial.textile | 2 +- examples/config/historical/runtime.properties | 2 +- examples/config/realtime/runtime.properties | 2 +- services/src/main/java/io/druid/cli/CliBroker.java | 2 +- services/src/main/java/io/druid/cli/CliCoordinator.java | 2 +- services/src/main/java/io/druid/cli/CliHadoopIndexer.java | 2 +- services/src/main/java/io/druid/cli/CliHistorical.java | 2 +- services/src/main/java/io/druid/cli/CliOverlord.java | 2 +- services/src/main/java/io/druid/cli/CliRealtime.java | 2 +- services/src/main/java/io/druid/cli/CliRealtimeExample.java | 2 +- 18 files changed, 23 insertions(+), 23 deletions(-) diff --git a/build.sh b/build.sh index 152b403ce2c..f9bd7723587 100755 --- a/build.sh +++ b/build.sh @@ -30,4 +30,4 @@ echo "For examples, see: " echo " " ls -1 examples/*/*sh echo " " -echo "See also http://druid.io/docs/0.6.38" +echo "See also http://druid.io/docs/0.6.39" diff --git a/docs/content/Booting-a-production-cluster.md b/docs/content/Booting-a-production-cluster.md index 478e1f409f0..ded3b7128d9 100644 --- a/docs/content/Booting-a-production-cluster.md +++ b/docs/content/Booting-a-production-cluster.md @@ -3,7 +3,7 @@ layout: doc_page --- # Booting a Single Node Cluster # -[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.38-bin.tar.gz). +[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.39-bin.tar.gz). The [ec2 run script](https://github.com/metamx/druid/blob/master/examples/bin/run_ec2.sh), run_ec2.sh, is located at 'examples/bin' if you have checked out the code, or at the root of the project if you've downloaded a tarball. The scripts rely on the [Amazon EC2 API Tools](http://aws.amazon.com/developertools/351), and you will need to set three environment variables: diff --git a/docs/content/Examples.md b/docs/content/Examples.md index 528f15876ad..00aa3c1d4ff 100644 --- a/docs/content/Examples.md +++ b/docs/content/Examples.md @@ -19,13 +19,13 @@ Clone Druid and build it: git clone https://github.com/metamx/druid.git druid cd druid git fetch --tags -git checkout druid-0.6.38 +git checkout druid-0.6.39 ./build.sh ``` ### Downloading the DSK (Druid Standalone Kit) -[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.38-bin.tar.gz) a stand-alone tarball and run it: +[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.39-bin.tar.gz) a stand-alone tarball and run it: ``` bash tar -xzf druid-services-0.X.X-bin.tar.gz diff --git a/docs/content/Realtime.md b/docs/content/Realtime.md index c604a1405c3..7ce371dfea4 100644 --- a/docs/content/Realtime.md +++ b/docs/content/Realtime.md @@ -27,7 +27,7 @@ druid.host=localhost druid.service=realtime druid.port=8083 -druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.38"] +druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.39"] druid.zk.service.host=localhost diff --git a/docs/content/Tutorial:-A-First-Look-at-Druid.md b/docs/content/Tutorial:-A-First-Look-at-Druid.md index d20d4e0ec83..8b5bde905d8 100644 --- a/docs/content/Tutorial:-A-First-Look-at-Druid.md +++ b/docs/content/Tutorial:-A-First-Look-at-Druid.md @@ -49,7 +49,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu ### Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.38-bin.tar.gz). Download this file to a directory of your choosing. +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.39-bin.tar.gz). Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -60,7 +60,7 @@ tar -zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.38 +cd druid-services-0.6.39 ``` You should see a bunch of files: diff --git a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md index cd6df42185c..ab4d91651ca 100644 --- a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md +++ b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md @@ -44,7 +44,7 @@ With real-world data, we recommend having a message bus such as [Apache Kafka](h #### Setting up Kafka -[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.38/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. +[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.39/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. Instructions for booting a Zookeeper and then Kafka cluster are available [here](http://kafka.apache.org/07/quickstart.html). diff --git a/docs/content/Tutorial:-The-Druid-Cluster.md b/docs/content/Tutorial:-The-Druid-Cluster.md index c8c7c8e4b93..6599031a0b2 100644 --- a/docs/content/Tutorial:-The-Druid-Cluster.md +++ b/docs/content/Tutorial:-The-Druid-Cluster.md @@ -13,7 +13,7 @@ In this tutorial, we will set up other types of Druid nodes as well as and exter If you followed the first tutorial, you should already have Druid downloaded. If not, let's go back and do that first. -You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.38-bin.tar.gz) +You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.39-bin.tar.gz) and untar the contents within by issuing: @@ -149,7 +149,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.38"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.39"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b @@ -238,7 +238,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.38","io.druid.extensions:druid-kafka-seven:0.6.38"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.39","io.druid.extensions:druid-kafka-seven:0.6.39"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/docs/content/Tutorial:-Webstream.md b/docs/content/Tutorial:-Webstream.md index 8d5e75a0da8..df7b4d2df24 100644 --- a/docs/content/Tutorial:-Webstream.md +++ b/docs/content/Tutorial:-Webstream.md @@ -37,7 +37,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.38-bin.tar.gz) +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.39-bin.tar.gz) Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -48,7 +48,7 @@ tar zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.38 +cd druid-services-0.6.39 ``` You should see a bunch of files: diff --git a/docs/content/Twitter-Tutorial.textile b/docs/content/Twitter-Tutorial.textile index ae36ab176b4..6fab02906a1 100644 --- a/docs/content/Twitter-Tutorial.textile +++ b/docs/content/Twitter-Tutorial.textile @@ -9,7 +9,7 @@ There are two ways to setup Druid: download a tarball, or build it from source. h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.38-bin.tar.gz. +We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.39-bin.tar.gz. Download this bad boy to a directory of your choosing. You can extract the awesomeness within by issuing: diff --git a/examples/config/historical/runtime.properties b/examples/config/historical/runtime.properties index ae40823c151..785f3901eb4 100644 --- a/examples/config/historical/runtime.properties +++ b/examples/config/historical/runtime.properties @@ -4,7 +4,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.38"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.39"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b diff --git a/examples/config/realtime/runtime.properties b/examples/config/realtime/runtime.properties index b92d608ed58..ae8675c610f 100644 --- a/examples/config/realtime/runtime.properties +++ b/examples/config/realtime/runtime.properties @@ -4,7 +4,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.38","io.druid.extensions:druid-kafka-seven:0.6.38","io.druid.extensions:druid-rabbitmq:0.6.38"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.39","io.druid.extensions:druid-kafka-seven:0.6.39","io.druid.extensions:druid-rabbitmq:0.6.39"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/services/src/main/java/io/druid/cli/CliBroker.java b/services/src/main/java/io/druid/cli/CliBroker.java index c69eba26445..4b6d6b93721 100644 --- a/services/src/main/java/io/druid/cli/CliBroker.java +++ b/services/src/main/java/io/druid/cli/CliBroker.java @@ -53,7 +53,7 @@ import java.util.List; */ @Command( name = "broker", - description = "Runs a broker node, see http://druid.io/docs/0.6.38/Broker.html for a description" + description = "Runs a broker node, see http://druid.io/docs/0.6.39/Broker.html for a description" ) public class CliBroker extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliCoordinator.java b/services/src/main/java/io/druid/cli/CliCoordinator.java index de50d2c1042..ea71bc7de58 100644 --- a/services/src/main/java/io/druid/cli/CliCoordinator.java +++ b/services/src/main/java/io/druid/cli/CliCoordinator.java @@ -60,7 +60,7 @@ import java.util.List; */ @Command( name = "coordinator", - description = "Runs the Coordinator, see http://druid.io/docs/0.6.38/Coordinator.html for a description." + description = "Runs the Coordinator, see http://druid.io/docs/0.6.39/Coordinator.html for a description." ) public class CliCoordinator extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java index 4f4bdd1336a..c9331bd0f04 100644 --- a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java +++ b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java @@ -41,7 +41,7 @@ import java.util.List; */ @Command( name = "hadoop", - description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.38/Batch-ingestion.html for a description." + description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.39/Batch-ingestion.html for a description." ) public class CliHadoopIndexer implements Runnable { diff --git a/services/src/main/java/io/druid/cli/CliHistorical.java b/services/src/main/java/io/druid/cli/CliHistorical.java index 746eb88113a..0072e5f3de5 100644 --- a/services/src/main/java/io/druid/cli/CliHistorical.java +++ b/services/src/main/java/io/druid/cli/CliHistorical.java @@ -42,7 +42,7 @@ import java.util.List; */ @Command( name = "historical", - description = "Runs a Historical node, see http://druid.io/docs/0.6.38/Historical.html for a description" + description = "Runs a Historical node, see http://druid.io/docs/0.6.39/Historical.html for a description" ) public class CliHistorical extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index feb29bccfcd..abb516803dc 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -93,7 +93,7 @@ import java.util.List; */ @Command( name = "overlord", - description = "Runs an Overlord node, see http://druid.io/docs/0.6.38/Indexing-Service.html for a description" + description = "Runs an Overlord node, see http://druid.io/docs/0.6.39/Indexing-Service.html for a description" ) public class CliOverlord extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtime.java b/services/src/main/java/io/druid/cli/CliRealtime.java index d414cf673f4..e5a68c647c0 100644 --- a/services/src/main/java/io/druid/cli/CliRealtime.java +++ b/services/src/main/java/io/druid/cli/CliRealtime.java @@ -30,7 +30,7 @@ import java.util.List; */ @Command( name = "realtime", - description = "Runs a realtime node, see http://druid.io/docs/0.6.38/Realtime.html for a description" + description = "Runs a realtime node, see http://druid.io/docs/0.6.39/Realtime.html for a description" ) public class CliRealtime extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtimeExample.java b/services/src/main/java/io/druid/cli/CliRealtimeExample.java index 66021e22c66..0ecb83e284a 100644 --- a/services/src/main/java/io/druid/cli/CliRealtimeExample.java +++ b/services/src/main/java/io/druid/cli/CliRealtimeExample.java @@ -42,7 +42,7 @@ import java.util.concurrent.Executor; */ @Command( name = "realtime", - description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.38/Realtime.html for a description" + description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.39/Realtime.html for a description" ) public class CliRealtimeExample extends ServerRunnable { From 2a98a4d3e0b6706c9553933d033145f495867162 Mon Sep 17 00:00:00 2001 From: fjy Date: Tue, 17 Dec 2013 17:48:42 -0800 Subject: [PATCH 127/189] [maven-release-plugin] prepare release druid-0.6.39 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index a558bc27751..eb4d75235fa 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39-SNAPSHOT + 0.6.39 diff --git a/common/pom.xml b/common/pom.xml index c5e1a28fd2a..a1c3a8b7834 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39-SNAPSHOT + 0.6.39 diff --git a/examples/pom.xml b/examples/pom.xml index 62ac9caa341..f941e500333 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39-SNAPSHOT + 0.6.39 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index cc85881c688..e1506b975c1 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39-SNAPSHOT + 0.6.39 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 9c1800a508f..29a33848ba9 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39-SNAPSHOT + 0.6.39 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 4c94d67e16d..2703aa29fba 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39-SNAPSHOT + 0.6.39 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index dab6df1961c..bd20e066210 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39-SNAPSHOT + 0.6.39 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index b118d103e36..ed60b704a07 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39-SNAPSHOT + 0.6.39 diff --git a/pom.xml b/pom.xml index 526caaf9e8a..64f1cf045c6 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.39-SNAPSHOT + 0.6.39 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.39 diff --git a/processing/pom.xml b/processing/pom.xml index f515f1f4289..ade2f8ecd28 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39-SNAPSHOT + 0.6.39 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 8cf219ea40e..23935dc15af 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.39-SNAPSHOT + 0.6.39 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 92562336cfc..773fd1aff02 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39-SNAPSHOT + 0.6.39 diff --git a/server/pom.xml b/server/pom.xml index c1c4ae6e806..5f335bab421 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39-SNAPSHOT + 0.6.39 diff --git a/services/pom.xml b/services/pom.xml index 3b20b66d8b1..48a4e5bcbcc 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.39-SNAPSHOT + 0.6.39 From 494ec530501310f42c7f1105c5330531c266cfa0 Mon Sep 17 00:00:00 2001 From: fjy Date: Tue, 17 Dec 2013 17:48:46 -0800 Subject: [PATCH 128/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index eb4d75235fa..07074721737 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39 + 0.6.40-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index a1c3a8b7834..0aa0c1221c6 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39 + 0.6.40-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index f941e500333..0b4f6d94a58 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39 + 0.6.40-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index e1506b975c1..c0d3015bc39 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39 + 0.6.40-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 29a33848ba9..58b73469c86 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39 + 0.6.40-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 2703aa29fba..f3890bc98ea 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39 + 0.6.40-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index bd20e066210..2bb456703a6 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39 + 0.6.40-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index ed60b704a07..b55cc2ad3a1 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39 + 0.6.40-SNAPSHOT diff --git a/pom.xml b/pom.xml index 64f1cf045c6..0163a2e34fe 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.39 + 0.6.40-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.39 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index ade2f8ecd28..3baa76d67be 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39 + 0.6.40-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 23935dc15af..f7264dd1a0a 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.39 + 0.6.40-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 773fd1aff02..c08f57ef0fc 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39 + 0.6.40-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index 5f335bab421..79fe38908ee 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.39 + 0.6.40-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index 48a4e5bcbcc..d6dd6892869 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.39 + 0.6.40-SNAPSHOT From fd427a33288aadc1b307bdc3f582d2cd926e9772 Mon Sep 17 00:00:00 2001 From: Hagen Rother Date: Wed, 18 Dec 2013 17:22:28 +0100 Subject: [PATCH 129/189] bump the maven shader plugin and config --- services/pom.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/pom.xml b/services/pom.xml index d6dd6892869..fd57bbc366d 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -68,7 +68,7 @@ org.apache.maven.plugins maven-shade-plugin - 1.6 + 2.2 package @@ -89,6 +89,9 @@ + + + From c969b37bac2aeb9da6a70b55fd2b9115bc49484b Mon Sep 17 00:00:00 2001 From: Ray Sayre Date: Wed, 18 Dec 2013 13:01:36 -0800 Subject: [PATCH 130/189] Remove unused payload column frosub-select that fails in postgres. Use boolean true/false for 'used' column so that queries run in postres --- .../main/java/io/druid/db/DatabaseRuleManager.java | 2 +- .../java/io/druid/db/DatabaseSegmentManager.java | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/io/druid/db/DatabaseRuleManager.java b/server/src/main/java/io/druid/db/DatabaseRuleManager.java index 0ba44c52c85..036acda23d6 100644 --- a/server/src/main/java/io/druid/db/DatabaseRuleManager.java +++ b/server/src/main/java/io/druid/db/DatabaseRuleManager.java @@ -207,7 +207,7 @@ public class DatabaseRuleManager String.format( "SELECT r.dataSource, r.payload " + "FROM %1$s r " - + "INNER JOIN(SELECT dataSource, max(version) as version, payload FROM %1$s GROUP BY dataSource) ds " + + "INNER JOIN(SELECT dataSource, max(version) as version FROM %1$s GROUP BY dataSource) ds " + "ON r.datasource = ds.datasource and r.version = ds.version", getRulesTable() ) diff --git a/server/src/main/java/io/druid/db/DatabaseSegmentManager.java b/server/src/main/java/io/druid/db/DatabaseSegmentManager.java index 203d082b806..3d68ad46978 100644 --- a/server/src/main/java/io/druid/db/DatabaseSegmentManager.java +++ b/server/src/main/java/io/druid/db/DatabaseSegmentManager.java @@ -213,7 +213,7 @@ public class DatabaseSegmentManager for (DataSegment segment : segments) { batch.add( String.format( - "UPDATE %s SET used=1 WHERE id = '%s'", + "UPDATE %s SET used=true WHERE id = '%s'", getSegmentsTable(), segment.getIdentifier() ) @@ -244,7 +244,7 @@ public class DatabaseSegmentManager public Void withHandle(Handle handle) throws Exception { handle.createStatement( - String.format("UPDATE %s SET used=1 WHERE id = :id", getSegmentsTable()) + String.format("UPDATE %s SET used=true WHERE id = :id", getSegmentsTable()) ) .bind("id", segmentId) .execute(); @@ -278,7 +278,7 @@ public class DatabaseSegmentManager public Void withHandle(Handle handle) throws Exception { handle.createStatement( - String.format("UPDATE %s SET used=0 WHERE dataSource = :dataSource", getSegmentsTable()) + String.format("UPDATE %s SET used=false WHERE dataSource = :dataSource", getSegmentsTable()) ) .bind("dataSource", ds) .execute(); @@ -308,7 +308,7 @@ public class DatabaseSegmentManager public Void withHandle(Handle handle) throws Exception { handle.createStatement( - String.format("UPDATE %s SET used=0 WHERE id = :segmentID", getSegmentsTable()) + String.format("UPDATE %s SET used=false WHERE id = :segmentID", getSegmentsTable()) ).bind("segmentID", segmentID) .execute(); @@ -408,7 +408,7 @@ public class DatabaseSegmentManager public List> withHandle(Handle handle) throws Exception { return handle.createQuery( - String.format("SELECT payload FROM %s WHERE used=1", getSegmentsTable()) + String.format("SELECT payload FROM %s WHERE used=true", getSegmentsTable()) ).list(); } } @@ -465,4 +465,4 @@ public class DatabaseSegmentManager private String getSegmentsTable() { return dbTables.get().getSegmentsTable(); } -} \ No newline at end of file +} From 892a42bafe6550cb2069850ab7f6bb1daa0969bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Wed, 18 Dec 2013 14:37:27 -0800 Subject: [PATCH 131/189] fix docs config casing --- docs/content/Configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/Configuration.md b/docs/content/Configuration.md index e317ac0ecd4..f01b5d41a0b 100644 --- a/docs/content/Configuration.md +++ b/docs/content/Configuration.md @@ -285,10 +285,10 @@ This deep storage is used to interface with Amazon's S3. |Property|Description|Default| |--------|-----------|-------| |`druid.storage.bucket`|S3 bucket name.|none| -|`druid.storage.basekey`|S3 object key prefix for storage.|none| +|`druid.storage.baseKey`|S3 object key prefix for storage.|none| |`druid.storage.disableAcl`|Boolean flag for ACL.|false| |`druid.storage.archiveBucket`|S3 bucket name for archiving when running the indexing-service *archive task*.|none| -|`druid.storage.archiveBasekey`|S3 object key prefix for archiving.|none| +|`druid.storage.archiveBaseKey`|S3 object key prefix for archiving.|none| #### HDFS Deep Storage From c8be38fe40de5351f4c2e1a2be3e6b076fff94da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Wed, 18 Dec 2013 14:57:14 -0800 Subject: [PATCH 132/189] back to baseKey --- .../java/io/druid/storage/s3/S3DataSegmentArchiver.java | 2 +- .../io/druid/storage/s3/S3DataSegmentArchiverConfig.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiver.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiver.java index 1c642f110bd..0da038352f0 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiver.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiver.java @@ -45,7 +45,7 @@ public class S3DataSegmentArchiver extends S3DataSegmentMover implements DataSeg public DataSegment archive(DataSegment segment) throws SegmentLoadingException { String targetS3Bucket = config.getArchiveBucket(); - String targetS3BaseKey = config.getArchiveBasekey(); + String targetS3BaseKey = config.getArchiveBaseKey(); return move( segment, diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiverConfig.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiverConfig.java index 9aeccb74afe..5eb33eb1b5d 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiverConfig.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentArchiverConfig.java @@ -27,15 +27,15 @@ public class S3DataSegmentArchiverConfig public String archiveBucket = ""; @JsonProperty - public String archiveBasekey = ""; + public String archiveBaseKey = ""; public String getArchiveBucket() { return archiveBucket; } - public String getArchiveBasekey() + public String getArchiveBaseKey() { - return archiveBasekey; + return archiveBaseKey; } } From 1ff855d744782d0b1eab8a82cee2c2bacb0d2853 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 18 Dec 2013 15:16:57 -0800 Subject: [PATCH 133/189] Fix MoveTask serde and ArchiveTask id creation --- .../indexing/common/task/ArchiveTask.java | 6 ++- .../druid/indexing/common/task/MoveTask.java | 6 +++ .../indexing/common/task/TaskSerdeTest.java | 53 ++++++++++++++++++- .../indexing/overlord/TaskLifecycleTest.java | 2 - 4 files changed, 63 insertions(+), 4 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/ArchiveTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/ArchiveTask.java index 97747e211ab..c863742e0ab 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/ArchiveTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/ArchiveTask.java @@ -45,7 +45,11 @@ public class ArchiveTask extends AbstractFixedIntervalTask @JsonProperty("interval") Interval interval ) { - super(id, dataSource, interval); + super( + TaskUtils.makeId(id, "archive", dataSource, interval), + dataSource, + interval + ); } @Override diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java index 371f0dc38a4..8e628b93188 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java @@ -112,4 +112,10 @@ public class MoveTask extends AbstractFixedIntervalTask return TaskStatus.success(getId()); } + + @JsonProperty + public Map getTargetLoadSpec() + { + return targetLoadSpec; + } } diff --git a/indexing-service/src/test/java/io/druid/indexing/common/task/TaskSerdeTest.java b/indexing-service/src/test/java/io/druid/indexing/common/task/TaskSerdeTest.java index 485b3202b54..77ae0af4b52 100644 --- a/indexing-service/src/test/java/io/druid/indexing/common/task/TaskSerdeTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/common/task/TaskSerdeTest.java @@ -294,7 +294,58 @@ public class TaskSerdeTest Assert.assertEquals(task.getGroupId(), task2.getGroupId()); Assert.assertEquals(task.getDataSource(), task2.getDataSource()); Assert.assertEquals(task.getInterval(), task2.getInterval()); - Assert.assertEquals(task.getSegments(), ((AppendTask) task2).getSegments()); + Assert.assertEquals(task.getSegments(), task2.getSegments()); + } + + @Test + public void testArchiveTaskSerde() throws Exception + { + final ArchiveTask task = new ArchiveTask( + null, + "foo", + new Interval("2010-01-01/P1D") + ); + + final ObjectMapper jsonMapper = new DefaultObjectMapper(); + final String json = jsonMapper.writeValueAsString(task); + + Thread.sleep(100); // Just want to run the clock a bit to make sure the task id doesn't change + final ArchiveTask task2 = (ArchiveTask) jsonMapper.readValue(json, Task.class); + + Assert.assertEquals("foo", task.getDataSource()); + Assert.assertEquals(new Interval("2010-01-01/P1D"), task.getInterval()); + + Assert.assertEquals(task.getId(), task2.getId()); + Assert.assertEquals(task.getGroupId(), task2.getGroupId()); + Assert.assertEquals(task.getDataSource(), task2.getDataSource()); + Assert.assertEquals(task.getInterval(), task2.getInterval()); + } + + @Test + public void testMoveTaskSerde() throws Exception + { + final MoveTask task = new MoveTask( + null, + "foo", + new Interval("2010-01-01/P1D"), + ImmutableMap.of("bucket", "hey", "baseKey", "what") + ); + + final ObjectMapper jsonMapper = new DefaultObjectMapper(); + final String json = jsonMapper.writeValueAsString(task); + + Thread.sleep(100); // Just want to run the clock a bit to make sure the task id doesn't change + final MoveTask task2 = (MoveTask) jsonMapper.readValue(json, Task.class); + + Assert.assertEquals("foo", task.getDataSource()); + Assert.assertEquals(new Interval("2010-01-01/P1D"), task.getInterval()); + Assert.assertEquals(ImmutableMap.of("bucket", "hey", "baseKey", "what"), task.getTargetLoadSpec()); + + Assert.assertEquals(task.getId(), task2.getId()); + Assert.assertEquals(task.getGroupId(), task2.getGroupId()); + Assert.assertEquals(task.getDataSource(), task2.getDataSource()); + Assert.assertEquals(task.getInterval(), task2.getInterval()); + Assert.assertEquals(task.getTargetLoadSpec(), task2.getTargetLoadSpec()); } @Test diff --git a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java index a873daa876f..85637d75c51 100644 --- a/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/overlord/TaskLifecycleTest.java @@ -78,9 +78,7 @@ import io.druid.timeline.DataSegment; import org.apache.commons.io.FileUtils; import org.easymock.EasyMock; import org.joda.time.DateTime; -import org.joda.time.Duration; import org.joda.time.Interval; -import org.joda.time.Period; import org.junit.After; import org.junit.Assert; import org.junit.Before; From fc4dd5119497d233e69458177f5e9ecf4a328d70 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 18 Dec 2013 15:33:02 -0800 Subject: [PATCH 134/189] Update versions --- build.sh | 2 +- docs/content/Booting-a-production-cluster.md | 2 +- docs/content/Examples.md | 4 ++-- docs/content/Realtime.md | 2 +- docs/content/Tutorial:-A-First-Look-at-Druid.md | 4 ++-- docs/content/Tutorial:-Loading-Your-Data-Part-2.md | 2 +- docs/content/Tutorial:-The-Druid-Cluster.md | 6 +++--- docs/content/Tutorial:-Webstream.md | 4 ++-- docs/content/Twitter-Tutorial.textile | 2 +- examples/config/historical/runtime.properties | 2 +- examples/config/realtime/runtime.properties | 2 +- services/src/main/java/io/druid/cli/CliBroker.java | 2 +- services/src/main/java/io/druid/cli/CliCoordinator.java | 2 +- services/src/main/java/io/druid/cli/CliHadoopIndexer.java | 2 +- services/src/main/java/io/druid/cli/CliHistorical.java | 2 +- services/src/main/java/io/druid/cli/CliOverlord.java | 2 +- services/src/main/java/io/druid/cli/CliRealtime.java | 2 +- services/src/main/java/io/druid/cli/CliRealtimeExample.java | 2 +- 18 files changed, 23 insertions(+), 23 deletions(-) diff --git a/build.sh b/build.sh index f9bd7723587..9b7478e592e 100755 --- a/build.sh +++ b/build.sh @@ -30,4 +30,4 @@ echo "For examples, see: " echo " " ls -1 examples/*/*sh echo " " -echo "See also http://druid.io/docs/0.6.39" +echo "See also http://druid.io/docs/0.6.40" diff --git a/docs/content/Booting-a-production-cluster.md b/docs/content/Booting-a-production-cluster.md index ded3b7128d9..bb00506700f 100644 --- a/docs/content/Booting-a-production-cluster.md +++ b/docs/content/Booting-a-production-cluster.md @@ -3,7 +3,7 @@ layout: doc_page --- # Booting a Single Node Cluster # -[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.39-bin.tar.gz). +[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.40-bin.tar.gz). The [ec2 run script](https://github.com/metamx/druid/blob/master/examples/bin/run_ec2.sh), run_ec2.sh, is located at 'examples/bin' if you have checked out the code, or at the root of the project if you've downloaded a tarball. The scripts rely on the [Amazon EC2 API Tools](http://aws.amazon.com/developertools/351), and you will need to set three environment variables: diff --git a/docs/content/Examples.md b/docs/content/Examples.md index 00aa3c1d4ff..7aff89ea644 100644 --- a/docs/content/Examples.md +++ b/docs/content/Examples.md @@ -19,13 +19,13 @@ Clone Druid and build it: git clone https://github.com/metamx/druid.git druid cd druid git fetch --tags -git checkout druid-0.6.39 +git checkout druid-0.6.40 ./build.sh ``` ### Downloading the DSK (Druid Standalone Kit) -[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.39-bin.tar.gz) a stand-alone tarball and run it: +[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.40-bin.tar.gz) a stand-alone tarball and run it: ``` bash tar -xzf druid-services-0.X.X-bin.tar.gz diff --git a/docs/content/Realtime.md b/docs/content/Realtime.md index 7ce371dfea4..980e1dfb773 100644 --- a/docs/content/Realtime.md +++ b/docs/content/Realtime.md @@ -27,7 +27,7 @@ druid.host=localhost druid.service=realtime druid.port=8083 -druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.39"] +druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.40"] druid.zk.service.host=localhost diff --git a/docs/content/Tutorial:-A-First-Look-at-Druid.md b/docs/content/Tutorial:-A-First-Look-at-Druid.md index 8b5bde905d8..1b35c0979e2 100644 --- a/docs/content/Tutorial:-A-First-Look-at-Druid.md +++ b/docs/content/Tutorial:-A-First-Look-at-Druid.md @@ -49,7 +49,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu ### Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.39-bin.tar.gz). Download this file to a directory of your choosing. +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.40-bin.tar.gz). Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -60,7 +60,7 @@ tar -zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.39 +cd druid-services-0.6.40 ``` You should see a bunch of files: diff --git a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md index ab4d91651ca..50fef2985f6 100644 --- a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md +++ b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md @@ -44,7 +44,7 @@ With real-world data, we recommend having a message bus such as [Apache Kafka](h #### Setting up Kafka -[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.39/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. +[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.40/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. Instructions for booting a Zookeeper and then Kafka cluster are available [here](http://kafka.apache.org/07/quickstart.html). diff --git a/docs/content/Tutorial:-The-Druid-Cluster.md b/docs/content/Tutorial:-The-Druid-Cluster.md index 6599031a0b2..348dfd1df53 100644 --- a/docs/content/Tutorial:-The-Druid-Cluster.md +++ b/docs/content/Tutorial:-The-Druid-Cluster.md @@ -13,7 +13,7 @@ In this tutorial, we will set up other types of Druid nodes as well as and exter If you followed the first tutorial, you should already have Druid downloaded. If not, let's go back and do that first. -You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.39-bin.tar.gz) +You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.40-bin.tar.gz) and untar the contents within by issuing: @@ -149,7 +149,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.39"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.40"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b @@ -238,7 +238,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.39","io.druid.extensions:druid-kafka-seven:0.6.39"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.40","io.druid.extensions:druid-kafka-seven:0.6.40"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/docs/content/Tutorial:-Webstream.md b/docs/content/Tutorial:-Webstream.md index df7b4d2df24..a9605f2fea4 100644 --- a/docs/content/Tutorial:-Webstream.md +++ b/docs/content/Tutorial:-Webstream.md @@ -37,7 +37,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.39-bin.tar.gz) +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.40-bin.tar.gz) Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -48,7 +48,7 @@ tar zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.39 +cd druid-services-0.6.40 ``` You should see a bunch of files: diff --git a/docs/content/Twitter-Tutorial.textile b/docs/content/Twitter-Tutorial.textile index 6fab02906a1..c82ea981ec4 100644 --- a/docs/content/Twitter-Tutorial.textile +++ b/docs/content/Twitter-Tutorial.textile @@ -9,7 +9,7 @@ There are two ways to setup Druid: download a tarball, or build it from source. h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.39-bin.tar.gz. +We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.40-bin.tar.gz. Download this bad boy to a directory of your choosing. You can extract the awesomeness within by issuing: diff --git a/examples/config/historical/runtime.properties b/examples/config/historical/runtime.properties index 785f3901eb4..fa2593c63a1 100644 --- a/examples/config/historical/runtime.properties +++ b/examples/config/historical/runtime.properties @@ -4,7 +4,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.39"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.40"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b diff --git a/examples/config/realtime/runtime.properties b/examples/config/realtime/runtime.properties index ae8675c610f..eae65e057f9 100644 --- a/examples/config/realtime/runtime.properties +++ b/examples/config/realtime/runtime.properties @@ -4,7 +4,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.39","io.druid.extensions:druid-kafka-seven:0.6.39","io.druid.extensions:druid-rabbitmq:0.6.39"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.40","io.druid.extensions:druid-kafka-seven:0.6.40","io.druid.extensions:druid-rabbitmq:0.6.40"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/services/src/main/java/io/druid/cli/CliBroker.java b/services/src/main/java/io/druid/cli/CliBroker.java index 4b6d6b93721..3a7c88d4426 100644 --- a/services/src/main/java/io/druid/cli/CliBroker.java +++ b/services/src/main/java/io/druid/cli/CliBroker.java @@ -53,7 +53,7 @@ import java.util.List; */ @Command( name = "broker", - description = "Runs a broker node, see http://druid.io/docs/0.6.39/Broker.html for a description" + description = "Runs a broker node, see http://druid.io/docs/0.6.40/Broker.html for a description" ) public class CliBroker extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliCoordinator.java b/services/src/main/java/io/druid/cli/CliCoordinator.java index ea71bc7de58..486511b1104 100644 --- a/services/src/main/java/io/druid/cli/CliCoordinator.java +++ b/services/src/main/java/io/druid/cli/CliCoordinator.java @@ -60,7 +60,7 @@ import java.util.List; */ @Command( name = "coordinator", - description = "Runs the Coordinator, see http://druid.io/docs/0.6.39/Coordinator.html for a description." + description = "Runs the Coordinator, see http://druid.io/docs/0.6.40/Coordinator.html for a description." ) public class CliCoordinator extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java index c9331bd0f04..2cdc9706cef 100644 --- a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java +++ b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java @@ -41,7 +41,7 @@ import java.util.List; */ @Command( name = "hadoop", - description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.39/Batch-ingestion.html for a description." + description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.40/Batch-ingestion.html for a description." ) public class CliHadoopIndexer implements Runnable { diff --git a/services/src/main/java/io/druid/cli/CliHistorical.java b/services/src/main/java/io/druid/cli/CliHistorical.java index 0072e5f3de5..e9c5ece9889 100644 --- a/services/src/main/java/io/druid/cli/CliHistorical.java +++ b/services/src/main/java/io/druid/cli/CliHistorical.java @@ -42,7 +42,7 @@ import java.util.List; */ @Command( name = "historical", - description = "Runs a Historical node, see http://druid.io/docs/0.6.39/Historical.html for a description" + description = "Runs a Historical node, see http://druid.io/docs/0.6.40/Historical.html for a description" ) public class CliHistorical extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index abb516803dc..aa68c14c6bb 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -93,7 +93,7 @@ import java.util.List; */ @Command( name = "overlord", - description = "Runs an Overlord node, see http://druid.io/docs/0.6.39/Indexing-Service.html for a description" + description = "Runs an Overlord node, see http://druid.io/docs/0.6.40/Indexing-Service.html for a description" ) public class CliOverlord extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtime.java b/services/src/main/java/io/druid/cli/CliRealtime.java index e5a68c647c0..70baabbc3c2 100644 --- a/services/src/main/java/io/druid/cli/CliRealtime.java +++ b/services/src/main/java/io/druid/cli/CliRealtime.java @@ -30,7 +30,7 @@ import java.util.List; */ @Command( name = "realtime", - description = "Runs a realtime node, see http://druid.io/docs/0.6.39/Realtime.html for a description" + description = "Runs a realtime node, see http://druid.io/docs/0.6.40/Realtime.html for a description" ) public class CliRealtime extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtimeExample.java b/services/src/main/java/io/druid/cli/CliRealtimeExample.java index 0ecb83e284a..f4b5c4ab5d8 100644 --- a/services/src/main/java/io/druid/cli/CliRealtimeExample.java +++ b/services/src/main/java/io/druid/cli/CliRealtimeExample.java @@ -42,7 +42,7 @@ import java.util.concurrent.Executor; */ @Command( name = "realtime", - description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.39/Realtime.html for a description" + description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.40/Realtime.html for a description" ) public class CliRealtimeExample extends ServerRunnable { From 231d7a6d1f508b8c9c59fa8dd92a5f7c3e318484 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 18 Dec 2013 15:35:50 -0800 Subject: [PATCH 135/189] [maven-release-plugin] prepare release druid-0.6.40 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 4 ++-- 14 files changed, 16 insertions(+), 16 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 07074721737..860546c1211 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40-SNAPSHOT + 0.6.40 diff --git a/common/pom.xml b/common/pom.xml index 0aa0c1221c6..2660c6802d0 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40-SNAPSHOT + 0.6.40 diff --git a/examples/pom.xml b/examples/pom.xml index 0b4f6d94a58..262c481711e 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40-SNAPSHOT + 0.6.40 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index c0d3015bc39..54ce731aaae 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40-SNAPSHOT + 0.6.40 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 58b73469c86..11a34c7defc 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40-SNAPSHOT + 0.6.40 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index f3890bc98ea..b7450874a27 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40-SNAPSHOT + 0.6.40 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 2bb456703a6..96588398515 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40-SNAPSHOT + 0.6.40 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index b55cc2ad3a1..11193312d9e 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40-SNAPSHOT + 0.6.40 diff --git a/pom.xml b/pom.xml index 0163a2e34fe..c6d3b367c95 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.40-SNAPSHOT + 0.6.40 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.40 diff --git a/processing/pom.xml b/processing/pom.xml index 3baa76d67be..707c405e38b 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40-SNAPSHOT + 0.6.40 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index f7264dd1a0a..209eeb1d2d0 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.40-SNAPSHOT + 0.6.40 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index c08f57ef0fc..22576cdf4cb 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40-SNAPSHOT + 0.6.40 diff --git a/server/pom.xml b/server/pom.xml index 79fe38908ee..64069421c54 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40-SNAPSHOT + 0.6.40 diff --git a/services/pom.xml b/services/pom.xml index fd57bbc366d..f20794390b0 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.40-SNAPSHOT + 0.6.40 @@ -90,7 +90,7 @@ - + From d0fd58bbae86c89b490c361e53276bf9f3ec1725 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 18 Dec 2013 15:35:55 -0800 Subject: [PATCH 136/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 860546c1211..f28c7dd5221 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40 + 0.6.41-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index 2660c6802d0..eb16f9251ba 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40 + 0.6.41-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index 262c481711e..d21dc9c836f 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40 + 0.6.41-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 54ce731aaae..e5a7af70ad8 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40 + 0.6.41-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 11a34c7defc..7718b246d27 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40 + 0.6.41-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index b7450874a27..0e25e7c69ed 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40 + 0.6.41-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 96588398515..08435a100c8 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40 + 0.6.41-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 11193312d9e..a312aa80608 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40 + 0.6.41-SNAPSHOT diff --git a/pom.xml b/pom.xml index c6d3b367c95..0d792e9bbb7 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.40 + 0.6.41-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.40 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index 707c405e38b..085af80a4da 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40 + 0.6.41-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 209eeb1d2d0..e9098809f3d 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.40 + 0.6.41-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 22576cdf4cb..7094612ff11 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40 + 0.6.41-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index 64069421c54..576b6060d71 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.40 + 0.6.41-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index f20794390b0..7bd5af6e7b4 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.40 + 0.6.41-SNAPSHOT From b7a184cb0602557f164945e05f64025fec93fbd3 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 18 Dec 2013 16:00:37 -0800 Subject: [PATCH 137/189] S3DataSegmentMover: Retries for S3 failures --- .../druid/storage/s3/S3DataSegmentMover.java | 74 ++++++++++++------- 1 file changed, 48 insertions(+), 26 deletions(-) diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java index a9baf1d40ab..f7cbd25ebdd 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java @@ -20,6 +20,7 @@ package io.druid.storage.s3; import com.google.common.base.Predicate; +import com.google.common.base.Throwables; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.inject.Inject; @@ -33,6 +34,7 @@ import org.jets3t.service.impl.rest.httpclient.RestS3Service; import org.jets3t.service.model.S3Object; import java.util.Map; +import java.util.concurrent.Callable; public class S3DataSegmentMover implements DataSegmentMover { @@ -97,34 +99,54 @@ public class S3DataSegmentMover implements DataSegmentMover } } - private void safeMove(String s3Bucket, String s3Path, String targetS3Bucket, String targetS3Path) - throws ServiceException, SegmentLoadingException + private void safeMove( + final String s3Bucket, + final String s3Path, + final String targetS3Bucket, + final String targetS3Path + ) throws ServiceException, SegmentLoadingException { - if (s3Client.isObjectInBucket(s3Bucket, s3Path)) { - log.info( - "Moving file[s3://%s/%s] to [s3://%s/%s]", - s3Bucket, - s3Path, - targetS3Bucket, - targetS3Path + try { + S3Utils.retryS3Operation( + new Callable() + { + @Override + public Void call() throws Exception + { + if (s3Client.isObjectInBucket(s3Bucket, s3Path)) { + log.info( + "Moving file[s3://%s/%s] to [s3://%s/%s]", + s3Bucket, + s3Path, + targetS3Bucket, + targetS3Path + ); + s3Client.moveObject(s3Bucket, s3Path, targetS3Bucket, new S3Object(targetS3Path), false); + } else { + // ensure object exists in target location + if (s3Client.isObjectInBucket(targetS3Bucket, targetS3Path)) { + log.info( + "Not moving file [s3://%s/%s], already present in target location [s3://%s/%s]", + s3Bucket, s3Path, + targetS3Bucket, targetS3Path + ); + } else { + throw new SegmentLoadingException( + "Unable to move file [s3://%s/%s] to [s3://%s/%s], not present in either source or target location", + s3Bucket, s3Path, + targetS3Bucket, targetS3Path + ); + } + } + return null; + } + } ); - s3Client.moveObject(s3Bucket, s3Path, targetS3Bucket, new S3Object(targetS3Path), false); - } else { - // ensure object exists in target location - if(s3Client.isObjectInBucket(targetS3Bucket, targetS3Path)) { - log.info( - "Not moving file [s3://%s/%s], already present in target location [s3://%s/%s]", - s3Bucket, s3Path, - targetS3Bucket, targetS3Path - ); - } - else { - throw new SegmentLoadingException( - "Unable to move file [s3://%s/%s] to [s3://%s/%s], not present in either source or target location", - s3Bucket, s3Path, - targetS3Bucket, targetS3Path - ); - } + } + catch (Exception e) { + Throwables.propagateIfInstanceOf(e, ServiceException.class); + Throwables.propagateIfInstanceOf(e, SegmentLoadingException.class); + throw Throwables.propagate(e); } } } From 00d651bf6fb0ac7306f48ccc35c97604f6d1503b Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 18 Dec 2013 16:05:27 -0800 Subject: [PATCH 138/189] [maven-release-plugin] prepare release druid-0.6.41 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index f28c7dd5221..74f38572a1f 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41-SNAPSHOT + 0.6.41 diff --git a/common/pom.xml b/common/pom.xml index eb16f9251ba..e7669f901ea 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41-SNAPSHOT + 0.6.41 diff --git a/examples/pom.xml b/examples/pom.xml index d21dc9c836f..aec1f6a8eed 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41-SNAPSHOT + 0.6.41 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index e5a7af70ad8..aca1d74ce5c 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41-SNAPSHOT + 0.6.41 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 7718b246d27..d199382bb09 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41-SNAPSHOT + 0.6.41 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 0e25e7c69ed..1219012a705 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41-SNAPSHOT + 0.6.41 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 08435a100c8..a4e50c0069d 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41-SNAPSHOT + 0.6.41 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index a312aa80608..f627d7246b0 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41-SNAPSHOT + 0.6.41 diff --git a/pom.xml b/pom.xml index 0d792e9bbb7..97fd7b205c0 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.41-SNAPSHOT + 0.6.41 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.41 diff --git a/processing/pom.xml b/processing/pom.xml index 085af80a4da..978142bf3fc 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41-SNAPSHOT + 0.6.41 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index e9098809f3d..a42b79cb7f0 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.41-SNAPSHOT + 0.6.41 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 7094612ff11..2dc00b1cc98 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41-SNAPSHOT + 0.6.41 diff --git a/server/pom.xml b/server/pom.xml index 576b6060d71..bce5ed868d6 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41-SNAPSHOT + 0.6.41 diff --git a/services/pom.xml b/services/pom.xml index 7bd5af6e7b4..ab3f1845195 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.41-SNAPSHOT + 0.6.41 From 8d8ec51c9cd6e7c897a7712b2662d71e5c4363eb Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 18 Dec 2013 16:05:33 -0800 Subject: [PATCH 139/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 74f38572a1f..00781634ef0 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41 + 0.6.42-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index e7669f901ea..3be3dc64d17 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41 + 0.6.42-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index aec1f6a8eed..ae847d7d6bf 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41 + 0.6.42-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index aca1d74ce5c..fb486c69856 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41 + 0.6.42-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index d199382bb09..a778774dd60 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41 + 0.6.42-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 1219012a705..70eecc8ab04 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41 + 0.6.42-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index a4e50c0069d..7c282e9ecf8 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41 + 0.6.42-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index f627d7246b0..7f6d33e03a4 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41 + 0.6.42-SNAPSHOT diff --git a/pom.xml b/pom.xml index 97fd7b205c0..e75dd1f81b4 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.41 + 0.6.42-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.41 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index 978142bf3fc..4e719dc1d8a 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41 + 0.6.42-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index a42b79cb7f0..a3b5e936cad 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.41 + 0.6.42-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 2dc00b1cc98..ae229afc5d8 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41 + 0.6.42-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index bce5ed868d6..173512727a5 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.41 + 0.6.42-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index ab3f1845195..fa580b17910 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.41 + 0.6.42-SNAPSHOT From 6478c9a16221c493356537d8d0d35466bbce66a1 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 18 Dec 2013 18:30:01 -0800 Subject: [PATCH 140/189] S3DataSegmentMover: No need to move files onto themselves --- .../druid/storage/s3/S3DataSegmentMover.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java index f7cbd25ebdd..fbf18df4c18 100644 --- a/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java +++ b/s3-extensions/src/main/java/io/druid/storage/s3/S3DataSegmentMover.java @@ -114,14 +114,18 @@ public class S3DataSegmentMover implements DataSegmentMover public Void call() throws Exception { if (s3Client.isObjectInBucket(s3Bucket, s3Path)) { - log.info( - "Moving file[s3://%s/%s] to [s3://%s/%s]", - s3Bucket, - s3Path, - targetS3Bucket, - targetS3Path - ); - s3Client.moveObject(s3Bucket, s3Path, targetS3Bucket, new S3Object(targetS3Path), false); + if (s3Bucket.equals(targetS3Bucket) && s3Path.equals(targetS3Path)) { + log.info("No need to move file[s3://%s/%s] onto itself", s3Bucket, s3Path); + } else { + log.info( + "Moving file[s3://%s/%s] to [s3://%s/%s]", + s3Bucket, + s3Path, + targetS3Bucket, + targetS3Path + ); + s3Client.moveObject(s3Bucket, s3Path, targetS3Bucket, new S3Object(targetS3Path), false); + } } else { // ensure object exists in target location if (s3Client.isObjectInBucket(targetS3Bucket, targetS3Path)) { From 6a740768d6bb659c1707987a1970193bee14132b Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 18 Dec 2013 18:38:22 -0800 Subject: [PATCH 141/189] [maven-release-plugin] prepare release druid-0.6.42 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 00781634ef0..91505a06bce 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42-SNAPSHOT + 0.6.42 diff --git a/common/pom.xml b/common/pom.xml index 3be3dc64d17..7bc99dc88b9 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42-SNAPSHOT + 0.6.42 diff --git a/examples/pom.xml b/examples/pom.xml index ae847d7d6bf..eb45adf80d5 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42-SNAPSHOT + 0.6.42 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index fb486c69856..4208546e265 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42-SNAPSHOT + 0.6.42 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index a778774dd60..b80cef04ad9 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42-SNAPSHOT + 0.6.42 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 70eecc8ab04..f9bed9c768f 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42-SNAPSHOT + 0.6.42 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 7c282e9ecf8..14e103168de 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42-SNAPSHOT + 0.6.42 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 7f6d33e03a4..9bce86c0a3b 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42-SNAPSHOT + 0.6.42 diff --git a/pom.xml b/pom.xml index e75dd1f81b4..e992cf5a07d 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.42-SNAPSHOT + 0.6.42 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.42 diff --git a/processing/pom.xml b/processing/pom.xml index 4e719dc1d8a..0fc7911d3ae 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42-SNAPSHOT + 0.6.42 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index a3b5e936cad..6589441cb9d 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.42-SNAPSHOT + 0.6.42 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index ae229afc5d8..c8ccd50748f 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42-SNAPSHOT + 0.6.42 diff --git a/server/pom.xml b/server/pom.xml index 173512727a5..036ae3c7970 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42-SNAPSHOT + 0.6.42 diff --git a/services/pom.xml b/services/pom.xml index fa580b17910..73fd9b9f2d1 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.42-SNAPSHOT + 0.6.42 From 25c3f28907cd8d85db5ff91a69051b47169a510b Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 18 Dec 2013 18:38:27 -0800 Subject: [PATCH 142/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 91505a06bce..5bade4d64d1 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42 + 0.6.43-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index 7bc99dc88b9..7cdef58b97a 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42 + 0.6.43-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index eb45adf80d5..eb880023fb2 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42 + 0.6.43-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 4208546e265..9085d8af3d2 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42 + 0.6.43-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index b80cef04ad9..7e68c822fb2 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42 + 0.6.43-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index f9bed9c768f..8b0a1708f53 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42 + 0.6.43-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 14e103168de..9295c043a24 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42 + 0.6.43-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 9bce86c0a3b..9356a120ce6 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42 + 0.6.43-SNAPSHOT diff --git a/pom.xml b/pom.xml index e992cf5a07d..9849c18e1bd 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.42 + 0.6.43-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.42 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index 0fc7911d3ae..d821fdd2572 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42 + 0.6.43-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 6589441cb9d..eebd245923f 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.42 + 0.6.43-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index c8ccd50748f..817fc5307e6 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42 + 0.6.43-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index 036ae3c7970..c88b542ce21 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.42 + 0.6.43-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index 73fd9b9f2d1..1d9cf5a4257 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.42 + 0.6.43-SNAPSHOT From c58fbfe40c5b4fc8a1c92f9475aafe07907ca1a2 Mon Sep 17 00:00:00 2001 From: Himadri Singh Date: Thu, 19 Dec 2013 10:49:38 +0530 Subject: [PATCH 143/189] minor fixes --- .../main/java/io/druid/initialization/Initialization.java | 7 +++++-- server/src/main/java/io/druid/server/StatusResource.java | 3 +-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/io/druid/initialization/Initialization.java b/server/src/main/java/io/druid/initialization/Initialization.java index 48fe618a5b8..5e8e0461202 100644 --- a/server/src/main/java/io/druid/initialization/Initialization.java +++ b/server/src/main/java/io/druid/initialization/Initialization.java @@ -111,9 +111,12 @@ public class Initialization */ public static Set getLoadedModules(Class clazz) { - return extensionsMap.get(clazz); + Set retVal = extensionsMap.get(clazz); + if (retVal == null) { + return Sets.newHashSet(); + } + return retVal; } - public synchronized static Collection getFromExtensions(ExtensionsConfig config, Class clazz) { diff --git a/server/src/main/java/io/druid/server/StatusResource.java b/server/src/main/java/io/druid/server/StatusResource.java index 7d8ec62d611..643d4d4fd74 100644 --- a/server/src/main/java/io/druid/server/StatusResource.java +++ b/server/src/main/java/io/druid/server/StatusResource.java @@ -148,7 +148,7 @@ public class StatusResource @Override public String toString() { - if (artifact.isEmpty()) { + if (artifact == null || artifact.isEmpty()) { return String.format(" - %s ", name); } else { return String.format(" - %s (%s-%s)", name, artifact, version); @@ -194,6 +194,5 @@ public class StatusResource { return usedMemory; } - } } From 52746b8ea6d15e1814adbc657c7d64ee1af2af36 Mon Sep 17 00:00:00 2001 From: Hagen Rother Date: Thu, 19 Dec 2013 06:54:04 +0100 Subject: [PATCH 144/189] fix hadoop intake's parser exception catching (was too specific) --- .../main/java/io/druid/indexer/HadoopDruidIndexerMapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexing-hadoop/src/main/java/io/druid/indexer/HadoopDruidIndexerMapper.java b/indexing-hadoop/src/main/java/io/druid/indexer/HadoopDruidIndexerMapper.java index 9f1bd030284..2eedaf76d31 100644 --- a/indexing-hadoop/src/main/java/io/druid/indexer/HadoopDruidIndexerMapper.java +++ b/indexing-hadoop/src/main/java/io/druid/indexer/HadoopDruidIndexerMapper.java @@ -62,7 +62,7 @@ public abstract class HadoopDruidIndexerMapper extends Mapper< try { inputRow = parser.parse(value.toString()); } - catch (IllegalArgumentException e) { + catch (Exception e) { if (config.isIgnoreInvalidRows()) { context.getCounter(HadoopDruidIndexerConfig.IndexJobCounters.INVALID_ROW_COUNTER).increment(1); return; // we're ignoring this invalid row From b1a9ecc1cfa44ac38120b886a6e9f9f43dcc3baf Mon Sep 17 00:00:00 2001 From: Himadri Singh Date: Thu, 19 Dec 2013 11:42:22 +0530 Subject: [PATCH 145/189] StatusResourceTest misses testing reloading of modules currently --- .../io/druid/server/StatusResourceTest.java | 72 +++++++++++++++++++ .../java/io/druid/server/TestDruidModule.java | 44 ++++++++++++ .../io.druid.initialization.DruidModule | 1 + 3 files changed, 117 insertions(+) create mode 100644 server/src/test/java/io/druid/server/StatusResourceTest.java create mode 100644 server/src/test/java/io/druid/server/TestDruidModule.java create mode 100644 server/src/test/resources/META-INF/services/io.druid.initialization.DruidModule diff --git a/server/src/test/java/io/druid/server/StatusResourceTest.java b/server/src/test/java/io/druid/server/StatusResourceTest.java new file mode 100644 index 00000000000..380afcdab9b --- /dev/null +++ b/server/src/test/java/io/druid/server/StatusResourceTest.java @@ -0,0 +1,72 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.server; + +import static io.druid.server.StatusResource.ModuleVersion; + +import com.google.inject.Injector; +import io.druid.initialization.DruidModule; +import io.druid.initialization.Initialization; +import io.druid.server.initialization.ExtensionsConfig; +import junit.framework.Assert; +import org.junit.Test; + +import java.util.Collection; +import java.util.List; + +/** + */ +public class StatusResourceTest +{ + + private Collection loadTestModule() + { + Injector baseInjector = Initialization.makeStartupInjector(); + return Initialization.getFromExtensions(baseInjector.getInstance(ExtensionsConfig.class), DruidModule.class); + } + + @Test + public void testLoadedModules() + { + final StatusResource resource = new StatusResource(); + List statusResourceModuleList; + + statusResourceModuleList = resource.doGet().getModules(); + Assert.assertEquals("No Modules should be loaded currently!", statusResourceModuleList.size(), 0); + + Collection modules = loadTestModule(); + statusResourceModuleList = resource.doGet().getModules(); + + Assert.assertEquals("Status should have all modules loaded!", statusResourceModuleList.size(), modules.size()); + + for (DruidModule module : modules) { + String moduleName = module.getClass().getCanonicalName(); + + boolean contains = Boolean.FALSE; + for (ModuleVersion version : statusResourceModuleList) { + if (version.getName().equals(moduleName)) { + contains = Boolean.TRUE; + } + } + Assert.assertTrue("Status resource should contains module " + moduleName, contains); + } + } + +} diff --git a/server/src/test/java/io/druid/server/TestDruidModule.java b/server/src/test/java/io/druid/server/TestDruidModule.java new file mode 100644 index 00000000000..0a428d808cd --- /dev/null +++ b/server/src/test/java/io/druid/server/TestDruidModule.java @@ -0,0 +1,44 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.server; + +import com.fasterxml.jackson.databind.Module; +import com.google.common.collect.ImmutableList; +import com.google.inject.Binder; +import io.druid.initialization.DruidModule; + +import java.util.List; + +/** + */ +public class TestDruidModule implements DruidModule +{ + @Override + public List getJacksonModules() + { + return ImmutableList.of(); + } + + @Override + public void configure(Binder binder) + { + // Do nothing + } +} diff --git a/server/src/test/resources/META-INF/services/io.druid.initialization.DruidModule b/server/src/test/resources/META-INF/services/io.druid.initialization.DruidModule new file mode 100644 index 00000000000..7acb2959046 --- /dev/null +++ b/server/src/test/resources/META-INF/services/io.druid.initialization.DruidModule @@ -0,0 +1 @@ +io.druid.server.TestDruidModule From cd1e7cc65d1184628e942fb8714f6468ff43290c Mon Sep 17 00:00:00 2001 From: Himadri Singh Date: Thu, 19 Dec 2013 12:01:18 +0530 Subject: [PATCH 146/189] Updated test --- .../io/druid/server/StatusResourceTest.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/server/src/test/java/io/druid/server/StatusResourceTest.java b/server/src/test/java/io/druid/server/StatusResourceTest.java index 380afcdab9b..2079c36b74d 100644 --- a/server/src/test/java/io/druid/server/StatusResourceTest.java +++ b/server/src/test/java/io/druid/server/StatusResourceTest.java @@ -19,8 +19,6 @@ package io.druid.server; -import static io.druid.server.StatusResource.ModuleVersion; - import com.google.inject.Injector; import io.druid.initialization.DruidModule; import io.druid.initialization.Initialization; @@ -30,6 +28,9 @@ import org.junit.Test; import java.util.Collection; import java.util.List; +import java.util.Set; + +import static io.druid.server.StatusResource.ModuleVersion; /** */ @@ -49,7 +50,10 @@ public class StatusResourceTest List statusResourceModuleList; statusResourceModuleList = resource.doGet().getModules(); - Assert.assertEquals("No Modules should be loaded currently!", statusResourceModuleList.size(), 0); + Assert.assertEquals( + "No Modules should be loaded currently! " + statusResourceModuleList, + statusResourceModuleList.size(), 0 + ); Collection modules = loadTestModule(); statusResourceModuleList = resource.doGet().getModules(); @@ -65,8 +69,17 @@ public class StatusResourceTest contains = Boolean.TRUE; } } - Assert.assertTrue("Status resource should contains module " + moduleName, contains); + Assert.assertTrue("Status resource should contain module " + moduleName, contains); } + + /* + * StatusResource only uses Initialization.getLoadedModules + */ + for (int i = 0; i < 5; i++) { + Set loadedModules = Initialization.getLoadedModules(DruidModule.class); + Assert.assertEquals("Set from loaded module should be same!", loadedModules, modules); + } + } } From 818aba458fcb247fd70ffb1be55d8b21d3f16788 Mon Sep 17 00:00:00 2001 From: Himadri Singh Date: Thu, 19 Dec 2013 12:14:06 +0530 Subject: [PATCH 147/189] Moved TestDruidModule --- .../io/druid/server/StatusResourceTest.java | 18 ++++++++ .../java/io/druid/server/TestDruidModule.java | 44 ------------------- .../io.druid.initialization.DruidModule | 2 +- 3 files changed, 19 insertions(+), 45 deletions(-) delete mode 100644 server/src/test/java/io/druid/server/TestDruidModule.java diff --git a/server/src/test/java/io/druid/server/StatusResourceTest.java b/server/src/test/java/io/druid/server/StatusResourceTest.java index 2079c36b74d..cff0fb4618e 100644 --- a/server/src/test/java/io/druid/server/StatusResourceTest.java +++ b/server/src/test/java/io/druid/server/StatusResourceTest.java @@ -19,6 +19,9 @@ package io.druid.server; +import com.fasterxml.jackson.databind.Module; +import com.google.common.collect.ImmutableList; +import com.google.inject.Binder; import com.google.inject.Injector; import io.druid.initialization.DruidModule; import io.druid.initialization.Initialization; @@ -79,7 +82,22 @@ public class StatusResourceTest Set loadedModules = Initialization.getLoadedModules(DruidModule.class); Assert.assertEquals("Set from loaded module should be same!", loadedModules, modules); } + } + public static class TestDruidModule implements DruidModule + { + @Override + public List getJacksonModules() + { + return ImmutableList.of(); + } + + @Override + public void configure(Binder binder) + { + // Do nothing + } } } + diff --git a/server/src/test/java/io/druid/server/TestDruidModule.java b/server/src/test/java/io/druid/server/TestDruidModule.java deleted file mode 100644 index 0a428d808cd..00000000000 --- a/server/src/test/java/io/druid/server/TestDruidModule.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Druid - a distributed column store. - * Copyright (C) 2012, 2013 Metamarkets Group Inc. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package io.druid.server; - -import com.fasterxml.jackson.databind.Module; -import com.google.common.collect.ImmutableList; -import com.google.inject.Binder; -import io.druid.initialization.DruidModule; - -import java.util.List; - -/** - */ -public class TestDruidModule implements DruidModule -{ - @Override - public List getJacksonModules() - { - return ImmutableList.of(); - } - - @Override - public void configure(Binder binder) - { - // Do nothing - } -} diff --git a/server/src/test/resources/META-INF/services/io.druid.initialization.DruidModule b/server/src/test/resources/META-INF/services/io.druid.initialization.DruidModule index 7acb2959046..09678a839eb 100644 --- a/server/src/test/resources/META-INF/services/io.druid.initialization.DruidModule +++ b/server/src/test/resources/META-INF/services/io.druid.initialization.DruidModule @@ -1 +1 @@ -io.druid.server.TestDruidModule +io.druid.server.StatusResourceTest$TestDruidModule From 6fbe67eeeafd7887b5959becd1977834b6178212 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 19 Dec 2013 13:04:59 -0800 Subject: [PATCH 148/189] IndexerDBCoordinator: Work around SELECT -> INSERT races when adding segments --- .../overlord/IndexerDBCoordinator.java | 71 +++++++++++-------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/IndexerDBCoordinator.java b/indexing-service/src/main/java/io/druid/indexing/overlord/IndexerDBCoordinator.java index f9db89b3fc9..7e5f3ef48dd 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/IndexerDBCoordinator.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/IndexerDBCoordinator.java @@ -42,6 +42,7 @@ import org.skife.jdbi.v2.ResultIterator; import org.skife.jdbi.v2.StatementContext; import org.skife.jdbi.v2.TransactionCallback; import org.skife.jdbi.v2.TransactionStatus; +import org.skife.jdbi.v2.exceptions.CallbackFailedException; import org.skife.jdbi.v2.tweak.HandleCallback; import java.io.IOException; @@ -169,39 +170,39 @@ public class IndexerDBCoordinator private boolean announceHistoricalSegment(final Handle handle, final DataSegment segment) throws IOException { try { - final List> exists = handle.createQuery( - String.format( - "SELECT id FROM %s WHERE id = :identifier", - dbTables.getSegmentsTable() - ) - ).bind( - "identifier", - segment.getIdentifier() - ).list(); - - if (!exists.isEmpty()) { + if (segmentExists(handle, segment)) { log.info("Found [%s] in DB, not updating DB", segment.getIdentifier()); return false; } - handle.createStatement( - String.format( - "INSERT INTO %s (id, dataSource, created_date, start, end, partitioned, version, used, payload) VALUES (:id, :dataSource, :created_date, :start, :end, :partitioned, :version, :used, :payload)", - dbTables.getSegmentsTable() - ) - ) - .bind("id", segment.getIdentifier()) - .bind("dataSource", segment.getDataSource()) - .bind("created_date", new DateTime().toString()) - .bind("start", segment.getInterval().getStart().toString()) - .bind("end", segment.getInterval().getEnd().toString()) - .bind("partitioned", segment.getShardSpec().getPartitionNum()) - .bind("version", segment.getVersion()) - .bind("used", true) - .bind("payload", jsonMapper.writeValueAsString(segment)) - .execute(); + // Try/catch to work around races due to SELECT -> INSERT. Avoid ON DUPLICATE KEY since it's not portable. + try { + handle.createStatement( + String.format( + "INSERT INTO %s (id, dataSource, created_date, start, end, partitioned, version, used, payload) " + + "VALUES (:id, :dataSource, :created_date, :start, :end, :partitioned, :version, :used, :payload)", + dbTables.getSegmentsTable() + ) + ) + .bind("id", segment.getIdentifier()) + .bind("dataSource", segment.getDataSource()) + .bind("created_date", new DateTime().toString()) + .bind("start", segment.getInterval().getStart().toString()) + .bind("end", segment.getInterval().getEnd().toString()) + .bind("partitioned", segment.getShardSpec().getPartitionNum()) + .bind("version", segment.getVersion()) + .bind("used", true) + .bind("payload", jsonMapper.writeValueAsString(segment)) + .execute(); - log.info("Published segment [%s] to DB", segment.getIdentifier()); + log.info("Published segment [%s] to DB", segment.getIdentifier()); + } catch (Exception e) { + if (e.getCause() instanceof SQLException && segmentExists(handle, segment)) { + log.info("Found [%s] in DB, not updating DB", segment.getIdentifier()); + } else { + throw e; + } + } } catch (IOException e) { log.error(e, "Exception inserting into DB"); @@ -211,6 +212,20 @@ public class IndexerDBCoordinator return true; } + private boolean segmentExists(final Handle handle, final DataSegment segment) { + final List> exists = handle.createQuery( + String.format( + "SELECT id FROM %s WHERE id = :identifier", + dbTables.getSegmentsTable() + ) + ).bind( + "identifier", + segment.getIdentifier() + ).list(); + + return !exists.isEmpty(); + } + public void updateSegmentMetadata(final Set segments) throws IOException { dbi.inTransaction( From 2d2fa319bd7ec58005ece620ec7c83c6ab4bab07 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 19 Dec 2013 13:05:10 -0800 Subject: [PATCH 149/189] pom.xml: Update emitter --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9849c18e1bd..f0b96500d4a 100644 --- a/pom.xml +++ b/pom.xml @@ -73,7 +73,7 @@ com.metamx emitter - 0.2.6 + 0.2.7 com.metamx From 566a3a6112381205b1883408e677ecbce0ef0d7d Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 19 Dec 2013 13:10:18 -0800 Subject: [PATCH 150/189] Indexing service: Break up segment actions Each one now one operates on at most a collection of segments that comprise a single partition. The main purpose of this change is to prevent audit log payload sizes from getting out of control. --- .../io/druid/indexing/common/TaskToolbox.java | 30 ++++++++++++++- .../common/actions/LocalTaskActionClient.java | 9 +++-- .../common/actions/SegmentInsertAction.java | 5 +-- .../actions/SegmentMetadataUpdateAction.java | 6 +-- .../common/actions/SegmentNukeAction.java | 6 +-- .../common/actions/TaskActionToolbox.java | 38 +++++++++++++++++++ .../indexing/common/task/DeleteTask.java | 3 +- .../indexing/common/task/HadoopIndexTask.java | 17 +++++---- .../druid/indexing/common/task/IndexTask.java | 2 +- .../druid/indexing/common/task/KillTask.java | 4 +- .../indexing/common/task/MergeTaskBase.java | 5 ++- .../druid/indexing/common/task/MoveTask.java | 10 +---- .../common/task/RealtimeIndexTask.java | 2 +- .../common/task/VersionConverterTask.java | 3 +- 14 files changed, 97 insertions(+), 43 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/TaskToolbox.java b/indexing-service/src/main/java/io/druid/indexing/common/TaskToolbox.java index eb34f4e4c1b..44f3c600f55 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/TaskToolbox.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/TaskToolbox.java @@ -20,10 +20,15 @@ package io.druid.indexing.common; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; import com.metamx.emitter.service.ServiceEmitter; import com.metamx.metrics.MonitorScheduler; import io.druid.client.ServerView; +import io.druid.indexing.common.actions.SegmentInsertAction; import io.druid.indexing.common.actions.TaskActionClient; import io.druid.indexing.common.actions.TaskActionClientFactory; import io.druid.indexing.common.config.TaskConfig; @@ -37,10 +42,14 @@ import io.druid.segment.loading.SegmentLoader; import io.druid.segment.loading.SegmentLoadingException; import io.druid.server.coordination.DataSegmentAnnouncer; import io.druid.timeline.DataSegment; +import org.joda.time.Interval; import java.io.File; +import java.io.IOException; +import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ExecutorService; /** @@ -167,7 +176,7 @@ public class TaskToolbox return objectMapper; } - public Map getSegments(List segments) + public Map fetchSegments(List segments) throws SegmentLoadingException { Map retVal = Maps.newLinkedHashMap(); @@ -178,6 +187,25 @@ public class TaskToolbox return retVal; } + public void pushSegments(Iterable segments) throws IOException { + // Request segment pushes for each set + final Multimap segmentMultimap = Multimaps.index( + segments, + new Function() + { + @Override + public Interval apply(DataSegment segment) + { + return segment.getInterval(); + } + } + ); + for (final Collection segmentCollection : segmentMultimap.asMap().values()) { + getTaskActionClient().submit(new SegmentInsertAction(ImmutableSet.copyOf(segmentCollection))); + } + + } + public File getTaskWorkDir() { return taskWorkDir; diff --git a/indexing-service/src/main/java/io/druid/indexing/common/actions/LocalTaskActionClient.java b/indexing-service/src/main/java/io/druid/indexing/common/actions/LocalTaskActionClient.java index 8bb23918b01..4dd445df80d 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/actions/LocalTaskActionClient.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/actions/LocalTaskActionClient.java @@ -19,6 +19,7 @@ package io.druid.indexing.common.actions; +import com.metamx.common.ISE; import com.metamx.emitter.EmittingLogger; import io.druid.indexing.common.task.Task; import io.druid.indexing.overlord.TaskStorage; @@ -45,21 +46,21 @@ public class LocalTaskActionClient implements TaskActionClient { log.info("Performing action for task[%s]: %s", task.getId(), taskAction); - final RetType ret = taskAction.perform(task, toolbox); - if (taskAction.isAudited()) { // Add audit log try { storage.addAuditLog(task, taskAction); } catch (Exception e) { + final String actionClass = taskAction.getClass().getName(); log.makeAlert(e, "Failed to record action in audit log") .addData("task", task.getId()) - .addData("actionClass", taskAction.getClass().getName()) + .addData("actionClass", actionClass) .emit(); + throw new ISE(e, "Failed to record action [%s] in audit log", actionClass); } } - return ret; + return taskAction.perform(task, toolbox); } } diff --git a/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentInsertAction.java b/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentInsertAction.java index aaad73b8a9f..5280e394f6f 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentInsertAction.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentInsertAction.java @@ -24,7 +24,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.type.TypeReference; import com.google.common.collect.ImmutableSet; -import com.metamx.common.ISE; import com.metamx.emitter.service.ServiceMetricEvent; import io.druid.indexing.common.task.Task; import io.druid.timeline.DataSegment; @@ -80,9 +79,7 @@ public class SegmentInsertAction implements TaskAction> @Override public Set perform(Task task, TaskActionToolbox toolbox) throws IOException { - if(!toolbox.taskLockCoversSegments(task, segments, allowOlderVersions)) { - throw new ISE("Segments not covered by locks for task[%s]: %s", task.getId(), segments); - } + toolbox.verifyTaskLocksAndSinglePartitionSettitude(task, segments, true); final Set retVal = toolbox.getIndexerDBCoordinator().announceHistoricalSegments(segments); diff --git a/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentMetadataUpdateAction.java b/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentMetadataUpdateAction.java index f996a2c6ab0..4356c80dc59 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentMetadataUpdateAction.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentMetadataUpdateAction.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.type.TypeReference; import com.google.common.collect.ImmutableSet; -import com.metamx.common.ISE; import com.metamx.emitter.service.ServiceMetricEvent; import io.druid.indexing.common.task.Task; import io.druid.timeline.DataSegment; @@ -42,10 +41,7 @@ public class SegmentMetadataUpdateAction implements TaskAction Task task, TaskActionToolbox toolbox ) throws IOException { - if(!toolbox.taskLockCoversSegments(task, segments, true)) { - throw new ISE("Segments not covered by locks for task: %s", task.getId()); - } - + toolbox.verifyTaskLocksAndSinglePartitionSettitude(task, segments, true); toolbox.getIndexerDBCoordinator().updateSegmentMetadata(segments); // Emit metrics diff --git a/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentNukeAction.java b/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentNukeAction.java index 6ac8dd1ccc4..54258df1c2d 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentNukeAction.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/actions/SegmentNukeAction.java @@ -24,7 +24,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.type.TypeReference; import com.google.common.collect.ImmutableSet; -import com.metamx.common.ISE; import com.metamx.emitter.service.ServiceMetricEvent; import io.druid.indexing.common.task.Task; import io.druid.timeline.DataSegment; @@ -59,10 +58,7 @@ public class SegmentNukeAction implements TaskAction @Override public Void perform(Task task, TaskActionToolbox toolbox) throws IOException { - if(!toolbox.taskLockCoversSegments(task, segments, true)) { - throw new ISE("Segments not covered by locks for task: %s", task.getId()); - } - + toolbox.verifyTaskLocksAndSinglePartitionSettitude(task, segments, true); toolbox.getIndexerDBCoordinator().deleteSegments(segments); // Emit metrics diff --git a/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskActionToolbox.java b/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskActionToolbox.java index a0b41e58a63..10c4d627462 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskActionToolbox.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskActionToolbox.java @@ -19,9 +19,11 @@ package io.druid.indexing.common.actions; +import com.google.api.client.repackaged.com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.inject.Inject; +import com.metamx.common.ISE; import com.metamx.emitter.service.ServiceEmitter; import io.druid.indexing.common.TaskLock; import io.druid.indexing.common.task.Task; @@ -65,6 +67,42 @@ public class TaskActionToolbox return emitter; } + public boolean segmentsAreFromSamePartitionSet( + final Set segments + ) + { + // Verify that these segments are all in the same partition set + + Preconditions.checkArgument(!segments.isEmpty(), "segments nonempty"); + final DataSegment firstSegment = segments.iterator().next(); + for (final DataSegment segment : segments) { + if (!segment.getDataSource().equals(firstSegment.getDataSource())) { + return false; + } + if (!segment.getInterval().equals(firstSegment.getInterval())) { + return false; + } + if (!segment.getVersion().equals(firstSegment.getVersion())) { + return false; + } + } + return true; + } + + public void verifyTaskLocksAndSinglePartitionSettitude( + final Task task, + final Set segments, + final boolean allowOlderVersions + ) + { + if (!taskLockCoversSegments(task, segments, allowOlderVersions)) { + throw new ISE("Segments not covered by locks for task: %s", task.getId()); + } + if (!segmentsAreFromSamePartitionSet(segments)) { + throw new ISE("Segments are not in the same partition set: %s", segments); + } + } + public boolean taskLockCoversSegments( final Task task, final Set segments, diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/DeleteTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/DeleteTask.java index 872ac3507bd..970818a6e9d 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/DeleteTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/DeleteTask.java @@ -22,6 +22,7 @@ package io.druid.indexing.common.task; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -103,7 +104,7 @@ public class DeleteTask extends AbstractFixedIntervalTask segment.getVersion() ); - toolbox.getTaskActionClient().submit(new SegmentInsertAction(ImmutableSet.of(uploadedSegment))); + toolbox.pushSegments(ImmutableList.of(uploadedSegment)); return TaskStatus.success(getId()); } diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/HadoopIndexTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/HadoopIndexTask.java index 400b088a693..233714f5c71 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/HadoopIndexTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/HadoopIndexTask.java @@ -24,10 +24,14 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.type.TypeReference; import com.google.api.client.util.Lists; +import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; import com.metamx.common.logger.Logger; import io.druid.common.utils.JodaUtils; import io.druid.indexer.HadoopDruidIndexerConfig; @@ -47,12 +51,15 @@ import io.tesla.aether.internal.DefaultTeslaAether; import org.joda.time.DateTime; import org.joda.time.Interval; +import javax.annotation.Nullable; import java.io.File; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.util.Arrays; +import java.util.Collection; import java.util.List; +import java.util.Map; public class HadoopIndexTask extends AbstractFixedIntervalTask { @@ -180,14 +187,10 @@ public class HadoopIndexTask extends AbstractFixedIntervalTask if (segments != null) { List publishedSegments = toolbox.getObjectMapper().readValue( - segments, new TypeReference>() - { - } + segments, + new TypeReference>() {} ); - // Request segment pushes - toolbox.getTaskActionClient().submit(new SegmentInsertAction(ImmutableSet.copyOf(publishedSegments))); - - // Done + toolbox.pushSegments(publishedSegments); return TaskStatus.success(getId()); } else { return TaskStatus.failure(getId()); diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java index 19cb791b77e..6e09e46373c 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java @@ -156,7 +156,7 @@ public class IndexTask extends AbstractFixedIntervalTask segments.add(segment); } } - toolbox.getTaskActionClient().submit(new SegmentInsertAction(segments)); + toolbox.pushSegments(segments); return TaskStatus.success(getId()); } diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/KillTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/KillTask.java index c77ddb21d96..b4858342981 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/KillTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/KillTask.java @@ -97,11 +97,9 @@ public class KillTask extends AbstractFixedIntervalTask // Kill segments for (DataSegment segment : unusedSegments) { toolbox.getDataSegmentKiller().kill(segment); + toolbox.getTaskActionClient().submit(new SegmentNukeAction(ImmutableSet.of(segment))); } - // Remove metadata for these segments - toolbox.getTaskActionClient().submit(new SegmentNukeAction(ImmutableSet.copyOf(unusedSegments))); - return TaskStatus.success(getId()); } } diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/MergeTaskBase.java b/indexing-service/src/main/java/io/druid/indexing/common/task/MergeTaskBase.java index d49d74b355b..40b07f72d71 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/MergeTaskBase.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/MergeTaskBase.java @@ -27,6 +27,7 @@ import com.google.common.base.Joiner; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -142,7 +143,7 @@ public abstract class MergeTaskBase extends AbstractFixedIntervalTask ); // download segments to merge - final Map gettedSegments = toolbox.getSegments(segments); + final Map gettedSegments = toolbox.fetchSegments(segments); // merge files together final File fileToUpload = merge(gettedSegments, new File(taskDir, "merged")); @@ -165,7 +166,7 @@ public abstract class MergeTaskBase extends AbstractFixedIntervalTask emitter.emit(builder.build("merger/uploadTime", System.currentTimeMillis() - uploadStart)); emitter.emit(builder.build("merger/mergeSize", uploadedSegment.getSize())); - toolbox.getTaskActionClient().submit(new SegmentInsertAction(ImmutableSet.of(uploadedSegment))); + toolbox.pushSegments(ImmutableList.of(uploadedSegment)); return TaskStatus.success(getId()); } diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java index 8e628b93188..da82ffa6608 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/MoveTask.java @@ -98,18 +98,12 @@ public class MoveTask extends AbstractFixedIntervalTask log.info("OK to move segment: %s", unusedSegment.getIdentifier()); } - List movedSegments = Lists.newLinkedList(); - // Move segments for (DataSegment segment : unusedSegments) { - movedSegments.add(toolbox.getDataSegmentMover().move(segment, targetLoadSpec)); + final DataSegment movedSegment = toolbox.getDataSegmentMover().move(segment, targetLoadSpec); + toolbox.getTaskActionClient().submit(new SegmentMetadataUpdateAction(ImmutableSet.of(movedSegment))); } - // Update metadata for moved segments - toolbox.getTaskActionClient().submit(new SegmentMetadataUpdateAction( - ImmutableSet.copyOf(movedSegments) - )); - return TaskStatus.success(getId()); } diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/RealtimeIndexTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/RealtimeIndexTask.java index f28d83c1caf..6ddc523140b 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/RealtimeIndexTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/RealtimeIndexTask.java @@ -418,7 +418,7 @@ public class RealtimeIndexTask extends AbstractTask @Override public void publishSegment(DataSegment segment) throws IOException { - taskToolbox.getTaskActionClient().submit(new SegmentInsertAction(ImmutableSet.of(segment))); + taskToolbox.pushSegments(ImmutableList.of(segment)); } } } diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/VersionConverterTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/VersionConverterTask.java index 52807861a12..75561f2408e 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/VersionConverterTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/VersionConverterTask.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Function; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.metamx.common.guava.FunctionalIterable; import com.metamx.common.logger.Logger; @@ -248,7 +249,7 @@ public class VersionConverterTask extends AbstractFixedIntervalTask } } - final Map localSegments = toolbox.getSegments(Arrays.asList(segment)); + final Map localSegments = toolbox.fetchSegments(Arrays.asList(segment)); final File location = localSegments.get(segment); final File outLocation = new File(location, "v9_out"); From 846c3da4ab2e94c17c39e636cb2ba10f174ec3e2 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 19 Dec 2013 13:21:41 -0800 Subject: [PATCH 151/189] Empty task intervals, and empty lock intervals, aren't useful. So prevent them from being created, through checks in AbstractFixedIntervalTask and TaskLockbox.tryLock. --- .../indexing/common/task/AbstractFixedIntervalTask.java | 4 +++- .../main/java/io/druid/indexing/overlord/TaskLockbox.java | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/AbstractFixedIntervalTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/AbstractFixedIntervalTask.java index d1d494f5c83..2d6687ed920 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/AbstractFixedIntervalTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/AbstractFixedIntervalTask.java @@ -21,6 +21,7 @@ package io.druid.indexing.common.task; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Preconditions; import io.druid.indexing.common.actions.LockTryAcquireAction; import io.druid.indexing.common.actions.TaskActionClient; import org.joda.time.Interval; @@ -58,7 +59,8 @@ public abstract class AbstractFixedIntervalTask extends AbstractTask ) { super(id, groupId, taskResource, dataSource); - this.interval = interval; + this.interval = Preconditions.checkNotNull(interval, "interval"); + Preconditions.checkArgument(interval.toDurationMillis() > 0, "interval empty"); } @Override diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskLockbox.java b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskLockbox.java index d486f37c0fc..7b3ffb09087 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/TaskLockbox.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/TaskLockbox.java @@ -19,6 +19,7 @@ package io.druid.indexing.overlord; +import com.google.api.client.repackaged.com.google.common.base.Preconditions; import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.base.Optional; @@ -109,6 +110,11 @@ public class TaskLockbox for (final Pair taskAndLock : byVersionOrdering.sortedCopy(storedLocks)) { final Task task = taskAndLock.lhs; final TaskLock savedTaskLock = taskAndLock.rhs; + if (savedTaskLock.getInterval().toDurationMillis() <= 0) { + // "Impossible", but you never know what crazy stuff can be restored from storage. + log.warn("WTF?! Got lock with empty interval for task: %s", task.getId()); + continue; + } uniqueTaskIds.add(task.getId()); final Optional acquiredTaskLock = tryLock( task, @@ -205,6 +211,7 @@ public class TaskLockbox giant.lock(); try { + Preconditions.checkArgument(interval.toDurationMillis() > 0, "interval empty"); final String dataSource = task.getDataSource(); final List foundPosses = findLockPossesForInterval(dataSource, interval); final TaskLockPosse posseToUse; From 1f4b99634fcd4dd17de55da7c1d56956d9cc6785 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 19 Dec 2013 16:11:30 -0800 Subject: [PATCH 152/189] Autoscaling: Move target count independent of actual count. This should let us grow and shrink the worker pool in chunks when necessary (like when a bunch of them go offline, or when there is a worker version change). --- .../indexing/overlord/RemoteTaskRunner.java | 9 +- .../overlord/scaling/AutoScalingData.java | 10 +- .../scaling/EC2AutoScalingStrategy.java | 8 +- .../SimpleResourceManagementStrategy.java | 399 ++++++++++-------- .../scaling/EC2AutoScalingStrategyTest.java | 2 - .../SimpleResourceManagementStrategyTest.java | 10 +- 6 files changed, 244 insertions(+), 194 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java b/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java index 8b823444319..2c482b775f9 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/RemoteTaskRunner.java @@ -27,6 +27,7 @@ import com.google.common.base.Preconditions; import com.google.common.base.Stopwatch; import com.google.common.base.Supplier; import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -251,26 +252,26 @@ public class RemoteTaskRunner implements TaskRunner, TaskLogStreamer @Override public Collection getWorkers() { - return zkWorkers.values(); + return ImmutableList.copyOf(zkWorkers.values()); } @Override public Collection getRunningTasks() { - return runningTasks.values(); + return ImmutableList.copyOf(runningTasks.values()); } @Override public Collection getPendingTasks() { - return pendingTasks.values(); + return ImmutableList.copyOf(pendingTasks.values()); } @Override public Collection getKnownTasks() { // Racey, since there is a period of time during assignment when a task is neither pending nor running - return Lists.newArrayList(Iterables.concat(pendingTasks.values(), runningTasks.values(), completeTasks.values())); + return ImmutableList.copyOf(Iterables.concat(pendingTasks.values(), runningTasks.values(), completeTasks.values())); } public ZkWorker findWorkerRunningTask(String taskId) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/AutoScalingData.java b/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/AutoScalingData.java index 7a0ab258310..34c45d66e71 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/AutoScalingData.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/AutoScalingData.java @@ -28,12 +28,10 @@ import java.util.List; public class AutoScalingData { private final List nodeIds; - private final List nodes; - public AutoScalingData(List nodeIds, List nodes) + public AutoScalingData(List nodeIds) { this.nodeIds = nodeIds; - this.nodes = nodes; } @JsonProperty @@ -42,17 +40,11 @@ public class AutoScalingData return nodeIds; } - public List getNodes() - { - return nodes; - } - @Override public String toString() { return "AutoScalingData{" + "nodeIds=" + nodeIds + - ", nodes=" + nodes + '}'; } } diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/EC2AutoScalingStrategy.java b/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/EC2AutoScalingStrategy.java index 57c2d875ac5..b59f3d1e74e 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/EC2AutoScalingStrategy.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/EC2AutoScalingStrategy.java @@ -125,8 +125,7 @@ public class EC2AutoScalingStrategy implements AutoScalingStrategy return input.getInstanceId(); } } - ), - result.getReservation().getInstances() + ) ); } catch (Exception e) { @@ -140,7 +139,7 @@ public class EC2AutoScalingStrategy implements AutoScalingStrategy public AutoScalingData terminate(List ips) { if (ips.isEmpty()) { - return new AutoScalingData(Lists.newArrayList(), Lists.newArrayList()); + return new AutoScalingData(Lists.newArrayList()); } DescribeInstancesResult result = amazonEC2Client.describeInstances( @@ -184,8 +183,7 @@ public class EC2AutoScalingStrategy implements AutoScalingStrategy return String.format("%s:%s", input, config.getWorkerPort()); } } - ), - instances + ) ); } catch (Exception e) { diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java b/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java index 50d612bd908..6b6fc613a7b 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java @@ -19,14 +19,17 @@ package io.druid.indexing.overlord.scaling; +import com.google.api.client.repackaged.com.google.common.base.Joiner; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Supplier; +import com.google.common.collect.Collections2; +import com.google.common.collect.FluentIterable; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.inject.Inject; -import com.metamx.common.guava.FunctionalIterable; +import com.metamx.common.ISE; import com.metamx.emitter.EmittingLogger; import io.druid.indexing.overlord.RemoteTaskRunnerWorkItem; import io.druid.indexing.overlord.TaskRunnerWorkItem; @@ -38,7 +41,6 @@ import org.joda.time.Duration; import java.util.Collection; import java.util.List; import java.util.Set; -import java.util.concurrent.ConcurrentSkipListSet; /** */ @@ -48,211 +50,188 @@ public class SimpleResourceManagementStrategy implements ResourceManagementStrat private final AutoScalingStrategy autoScalingStrategy; private final SimpleResourceManagementConfig config; - private final Supplier workerSetupdDataRef; + private final Supplier workerSetupDataRef; private final ScalingStats scalingStats; - private final ConcurrentSkipListSet currentlyProvisioning = new ConcurrentSkipListSet(); - private final ConcurrentSkipListSet currentlyTerminating = new ConcurrentSkipListSet(); + private final Object lock = new Object(); + private final Set currentlyProvisioning = Sets.newHashSet(); + private final Set currentlyTerminating = Sets.newHashSet(); + private final Predicate isLazyWorker = new Predicate() + { + @Override + public boolean apply(ZkWorker input) + { + return input.getRunningTasks().isEmpty() + && System.currentTimeMillis() - input.getLastCompletedTaskTime().getMillis() + >= config.getWorkerIdleTimeout().toStandardDuration().getMillis(); + } + }; - private volatile DateTime lastProvisionTime = new DateTime(); - private volatile DateTime lastTerminateTime = new DateTime(); + private int targetWorkerCount = -1; + private DateTime lastProvisionTime = new DateTime(); + private DateTime lastTerminateTime = new DateTime(); @Inject public SimpleResourceManagementStrategy( AutoScalingStrategy autoScalingStrategy, SimpleResourceManagementConfig config, - Supplier workerSetupdDataRef + Supplier workerSetupDataRef ) { this.autoScalingStrategy = autoScalingStrategy; this.config = config; - this.workerSetupdDataRef = workerSetupdDataRef; + this.workerSetupDataRef = workerSetupDataRef; this.scalingStats = new ScalingStats(config.getNumEventsToTrack()); } @Override public boolean doProvision(Collection pendingTasks, Collection zkWorkers) { - final WorkerSetupData workerSetupData = workerSetupdDataRef.get(); + synchronized (lock) { + boolean didProvision = false; + final WorkerSetupData workerSetupData = workerSetupDataRef.get(); + final Predicate isValidWorker = createValidWorkerPredicate(workerSetupData); + final int currValidWorkers = Collections2.filter(zkWorkers, isValidWorker).size(); - final String minVersion = workerSetupData.getMinVersion() == null - ? config.getWorkerVersion() - : workerSetupData.getMinVersion(); - int maxNumWorkers = workerSetupData.getMaxNumWorkers(); - - int currValidWorkers = 0; - for (ZkWorker zkWorker : zkWorkers) { - if (zkWorker.isValidVersion(minVersion)) { - currValidWorkers++; - } - } - - if (currValidWorkers >= maxNumWorkers) { - log.debug( - "Cannot scale anymore. Num workers = %d, Max num workers = %d", - zkWorkers.size(), - workerSetupdDataRef.get().getMaxNumWorkers() - ); - return false; - } - - List workerNodeIds = autoScalingStrategy.ipToIdLookup( - Lists.newArrayList( - Iterables.transform( - zkWorkers, - new Function() - { - @Override - public String apply(ZkWorker input) + final List workerNodeIds = autoScalingStrategy.ipToIdLookup( + Lists.newArrayList( + Iterables.transform( + zkWorkers, + new Function() { - return input.getWorker().getIp(); + @Override + public String apply(ZkWorker input) + { + return input.getWorker().getIp(); + } } - } - ) - ) - ); + ) + ) + ); + currentlyProvisioning.removeAll(workerNodeIds); - currentlyProvisioning.removeAll(workerNodeIds); - boolean nothingProvisioning = currentlyProvisioning.isEmpty(); + updateTargetWorkerCount(pendingTasks, zkWorkers); - if (nothingProvisioning) { - if (hasTaskPendingBeyondThreshold(pendingTasks)) { - AutoScalingData provisioned = autoScalingStrategy.provision(); + if (currentlyProvisioning.isEmpty()) { + int want = targetWorkerCount - (currValidWorkers + currentlyProvisioning.size()); + while (want > 0) { + final AutoScalingData provisioned = autoScalingStrategy.provision(); + if (provisioned == null) { + break; + } else { + currentlyProvisioning.addAll(provisioned.getNodeIds()); + lastProvisionTime = new DateTime(); + scalingStats.addProvisionEvent(provisioned); + want -= provisioned.getNodeIds().size(); + didProvision = true; + } + } + } else { + Duration durSinceLastProvision = new Duration(lastProvisionTime, new DateTime()); - if (provisioned != null) { - currentlyProvisioning.addAll(provisioned.getNodeIds()); - lastProvisionTime = new DateTime(); - scalingStats.addProvisionEvent(provisioned); + log.info("%s provisioning. Current wait time: %s", currentlyProvisioning, durSinceLastProvision); - return true; + if (durSinceLastProvision.isLongerThan(config.getMaxScalingDuration().toStandardDuration())) { + log.makeAlert("Worker node provisioning taking too long!") + .addData("millisSinceLastProvision", durSinceLastProvision.getMillis()) + .addData("provisioningCount", currentlyProvisioning.size()) + .emit(); + + List nodeIps = autoScalingStrategy.idToIpLookup(Lists.newArrayList(currentlyProvisioning)); + autoScalingStrategy.terminate(nodeIps); + currentlyProvisioning.clear(); } } - } else { - Duration durSinceLastProvision = new Duration(lastProvisionTime, new DateTime()); - log.info( - "%s still provisioning. Wait for all provisioned nodes to complete before requesting new worker. Current wait time: %s", - currentlyProvisioning, - durSinceLastProvision - ); - - if (durSinceLastProvision.isLongerThan(config.getMaxScalingDuration().toStandardDuration())) { - log.makeAlert("Worker node provisioning taking too long!") - .addData("millisSinceLastProvision", durSinceLastProvision.getMillis()) - .addData("provisioningCount", currentlyProvisioning.size()) - .emit(); - - List nodeIps = autoScalingStrategy.idToIpLookup(Lists.newArrayList(currentlyProvisioning)); - autoScalingStrategy.terminate(nodeIps); - currentlyProvisioning.clear(); - } + return didProvision; } - - return false; } @Override public boolean doTerminate(Collection pendingTasks, Collection zkWorkers) { - Set workerNodeIds = Sets.newHashSet( - autoScalingStrategy.ipToIdLookup( - Lists.newArrayList( - Iterables.transform( - zkWorkers, - new Function() - { - @Override - public String apply(ZkWorker input) + synchronized (lock) { + boolean didTerminate = false; + final Set workerNodeIds = Sets.newHashSet( + autoScalingStrategy.ipToIdLookup( + Lists.newArrayList( + Iterables.transform( + zkWorkers, + new Function() { - return input.getWorker().getIp(); + @Override + public String apply(ZkWorker input) + { + return input.getWorker().getIp(); + } } - } - ) - ) - ) - ); - - Set stillExisting = Sets.newHashSet(); - for (String s : currentlyTerminating) { - if (workerNodeIds.contains(s)) { - stillExisting.add(s); - } - } - currentlyTerminating.clear(); - currentlyTerminating.addAll(stillExisting); - boolean nothingTerminating = currentlyTerminating.isEmpty(); - - if (nothingTerminating) { - final int minNumWorkers = workerSetupdDataRef.get().getMinNumWorkers(); - if (zkWorkers.size() <= minNumWorkers) { - log.info("Only [%d <= %d] nodes in the cluster, not terminating anything.", zkWorkers.size(), minNumWorkers); - return false; - } - - List thoseLazyWorkers = Lists.newArrayList( - FunctionalIterable - .create(zkWorkers) - .filter( - new Predicate() - { - @Override - public boolean apply(ZkWorker input) - { - return input.getRunningTasks().isEmpty() - && System.currentTimeMillis() - input.getLastCompletedTaskTime().getMillis() - >= config.getWorkerIdleTimeout().toStandardDuration().getMillis(); - } - } + ) ) - ); - - int maxPossibleNodesTerminated = zkWorkers.size() - minNumWorkers; - int numNodesToTerminate = Math.min(maxPossibleNodesTerminated, thoseLazyWorkers.size()); - if (numNodesToTerminate <= 0) { - log.info("Found no nodes to terminate."); - return false; - } - - AutoScalingData terminated = autoScalingStrategy.terminate( - Lists.transform( - thoseLazyWorkers.subList(0, numNodesToTerminate), - new Function() - { - @Override - public String apply(ZkWorker input) - { - return input.getWorker().getIp(); - } - } ) ); - if (terminated != null) { - currentlyTerminating.addAll(terminated.getNodeIds()); - lastTerminateTime = new DateTime(); - scalingStats.addTerminateEvent(terminated); - - return true; + final Set stillExisting = Sets.newHashSet(); + for (String s : currentlyTerminating) { + if (workerNodeIds.contains(s)) { + stillExisting.add(s); + } } - } else { - Duration durSinceLastTerminate = new Duration(lastTerminateTime, new DateTime()); + currentlyTerminating.clear(); + currentlyTerminating.addAll(stillExisting); - log.info( - "%s still terminating. Wait for all nodes to terminate before trying again.", - currentlyTerminating - ); + updateTargetWorkerCount(pendingTasks, zkWorkers); - if (durSinceLastTerminate.isLongerThan(config.getMaxScalingDuration().toStandardDuration())) { - log.makeAlert("Worker node termination taking too long!") - .addData("millisSinceLastTerminate", durSinceLastTerminate.getMillis()) - .addData("terminatingCount", currentlyTerminating.size()) - .emit(); + if (currentlyTerminating.isEmpty()) { + final int want = zkWorkers.size() - targetWorkerCount; + if (want > 0) { + final List laziestWorkerIps = + FluentIterable.from(zkWorkers) + .filter(isLazyWorker) + .limit(want) + .transform( + new Function() + { + @Override + public String apply(ZkWorker zkWorker) + { + return zkWorker.getWorker().getIp(); + } + } + ) + .toList(); - currentlyTerminating.clear(); + log.info( + "Terminating %,d workers (wanted %,d): %s", + laziestWorkerIps.size(), + want, + Joiner.on(", ").join(laziestWorkerIps) + ); + + final AutoScalingData terminated = autoScalingStrategy.terminate(laziestWorkerIps); + if (terminated != null) { + currentlyTerminating.addAll(terminated.getNodeIds()); + lastTerminateTime = new DateTime(); + scalingStats.addTerminateEvent(terminated); + didTerminate = true; + } + } + } else { + Duration durSinceLastTerminate = new Duration(lastTerminateTime, new DateTime()); + + log.info("%s terminating. Current wait time: ", currentlyTerminating, durSinceLastTerminate); + + if (durSinceLastTerminate.isLongerThan(config.getMaxScalingDuration().toStandardDuration())) { + log.makeAlert("Worker node termination taking too long!") + .addData("millisSinceLastTerminate", durSinceLastTerminate.getMillis()) + .addData("terminatingCount", currentlyTerminating.size()) + .emit(); + + currentlyTerminating.clear(); + } } + + return didTerminate; } - - return false; } @Override @@ -261,16 +240,98 @@ public class SimpleResourceManagementStrategy implements ResourceManagementStrat return scalingStats; } - private boolean hasTaskPendingBeyondThreshold(Collection pendingTasks) + private Predicate createValidWorkerPredicate(final WorkerSetupData workerSetupData) { - long now = System.currentTimeMillis(); - for (TaskRunnerWorkItem pendingTask : pendingTasks) { - final Duration durationSinceInsertion = new Duration(pendingTask.getQueueInsertionTime().getMillis(), now); - final Duration timeoutDuration = config.getPendingTaskTimeout().toStandardDuration(); - if (durationSinceInsertion.isEqual(timeoutDuration) || durationSinceInsertion.isLongerThan(timeoutDuration)) { - return true; + return new Predicate() + { + @Override + public boolean apply(ZkWorker zkWorker) + { + final String minVersion = workerSetupData.getMinVersion() != null + ? workerSetupData.getMinVersion() + : config.getWorkerVersion(); + if (minVersion == null) { + throw new ISE("No minVersion found! It should be set in your runtime properties or configuration database."); + } + return zkWorker.isValidVersion(minVersion); + } + }; + } + + private void updateTargetWorkerCount( + final Collection pendingTasks, + final Collection zkWorkers + ) + { + synchronized (lock) { + final WorkerSetupData workerSetupData = workerSetupDataRef.get(); + + if (targetWorkerCount < 0) { + // Initialize to size of current worker pool + targetWorkerCount = zkWorkers.size(); + log.info( + "Starting with %,d workers (min = %,d, max = %,d).", + targetWorkerCount, + workerSetupData.getMinNumWorkers(), + workerSetupData.getMaxNumWorkers() + ); + } + + final Collection validWorkers = Collections2.filter( + zkWorkers, + createValidWorkerPredicate(workerSetupData) + ); + final boolean atSteadyState = currentlyProvisioning.isEmpty() + && currentlyTerminating.isEmpty() + && validWorkers.size() == targetWorkerCount; + final boolean shouldScaleUp = atSteadyState + && hasTaskPendingBeyondThreshold(pendingTasks) + && targetWorkerCount < workerSetupData.getMaxNumWorkers(); + final boolean shouldScaleDown = atSteadyState + && Iterables.any(validWorkers, isLazyWorker) + && targetWorkerCount > workerSetupData.getMinNumWorkers(); + if (shouldScaleUp) { + targetWorkerCount++; + log.info( + "I think we should scale up to %,d workers (current = %,d, min = %,d, max = %,d).", + targetWorkerCount, + validWorkers.size(), + workerSetupData.getMinNumWorkers(), + workerSetupData.getMaxNumWorkers() + ); + } else if (shouldScaleDown) { + targetWorkerCount--; + log.info( + "I think we should scale down to %,d workers (current = %,d, min = %,d, max = %,d).", + targetWorkerCount, + validWorkers.size(), + workerSetupData.getMinNumWorkers(), + workerSetupData.getMaxNumWorkers() + ); + } else { + log.info( + "Our target is %,d workers, and I'm okay with that (current = %,d, min = %,d, max = %,d).", + targetWorkerCount, + validWorkers.size(), + workerSetupData.getMinNumWorkers(), + workerSetupData.getMaxNumWorkers() + ); } } - return false; + } + + private boolean hasTaskPendingBeyondThreshold(Collection pendingTasks) + { + synchronized (lock) { + long now = System.currentTimeMillis(); + for (TaskRunnerWorkItem pendingTask : pendingTasks) { + final Duration durationSinceInsertion = new Duration(pendingTask.getQueueInsertionTime().getMillis(), now); + final Duration timeoutDuration = config.getPendingTaskTimeout().toStandardDuration(); + if (durationSinceInsertion.isEqual(timeoutDuration) || durationSinceInsertion.isLongerThan(timeoutDuration)) { + return true; + } + } + return false; + } } } diff --git a/indexing-service/src/test/java/io/druid/indexing/overlord/scaling/EC2AutoScalingStrategyTest.java b/indexing-service/src/test/java/io/druid/indexing/overlord/scaling/EC2AutoScalingStrategyTest.java index d0b5edb5ca1..e6cd52c80ac 100644 --- a/indexing-service/src/test/java/io/druid/indexing/overlord/scaling/EC2AutoScalingStrategyTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/overlord/scaling/EC2AutoScalingStrategyTest.java @@ -126,13 +126,11 @@ public class EC2AutoScalingStrategyTest AutoScalingData created = strategy.provision(); Assert.assertEquals(created.getNodeIds().size(), 1); - Assert.assertEquals(created.getNodes().size(), 1); Assert.assertEquals("theInstance", created.getNodeIds().get(0)); AutoScalingData deleted = strategy.terminate(Arrays.asList("dummyIP")); Assert.assertEquals(deleted.getNodeIds().size(), 1); - Assert.assertEquals(deleted.getNodes().size(), 1); Assert.assertEquals(String.format("%s:8080", IP), deleted.getNodeIds().get(0)); } } diff --git a/indexing-service/src/test/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategyTest.java b/indexing-service/src/test/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategyTest.java index 1f3f4a44eee..10c7bf77882 100644 --- a/indexing-service/src/test/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategyTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategyTest.java @@ -105,7 +105,7 @@ public class SimpleResourceManagementStrategyTest EasyMock.expect(autoScalingStrategy.ipToIdLookup(EasyMock.>anyObject())) .andReturn(Lists.newArrayList()); EasyMock.expect(autoScalingStrategy.provision()).andReturn( - new AutoScalingData(Lists.newArrayList(), Lists.newArrayList()) + new AutoScalingData(Lists.newArrayList("aNode")) ); EasyMock.replay(autoScalingStrategy); @@ -133,7 +133,7 @@ public class SimpleResourceManagementStrategyTest EasyMock.expect(autoScalingStrategy.ipToIdLookup(EasyMock.>anyObject())) .andReturn(Lists.newArrayList()).times(2); EasyMock.expect(autoScalingStrategy.provision()).andReturn( - new AutoScalingData(Lists.newArrayList("fake"), Lists.newArrayList("faker")) + new AutoScalingData(Lists.newArrayList("fake")) ); EasyMock.replay(autoScalingStrategy); @@ -190,7 +190,7 @@ public class SimpleResourceManagementStrategyTest EasyMock.expect(autoScalingStrategy.terminate(EasyMock.>anyObject())) .andReturn(null); EasyMock.expect(autoScalingStrategy.provision()).andReturn( - new AutoScalingData(Lists.newArrayList("fake"), Lists.newArrayList("faker")) + new AutoScalingData(Lists.newArrayList("fake")) ); EasyMock.replay(autoScalingStrategy); @@ -242,7 +242,7 @@ public class SimpleResourceManagementStrategyTest EasyMock.expect(autoScalingStrategy.ipToIdLookup(EasyMock.>anyObject())) .andReturn(Lists.newArrayList()); EasyMock.expect(autoScalingStrategy.terminate(EasyMock.>anyObject())).andReturn( - new AutoScalingData(Lists.newArrayList(), Lists.newArrayList()) + new AutoScalingData(Lists.newArrayList()) ); EasyMock.replay(autoScalingStrategy); @@ -272,7 +272,7 @@ public class SimpleResourceManagementStrategyTest EasyMock.expect(autoScalingStrategy.ipToIdLookup(EasyMock.>anyObject())) .andReturn(Lists.newArrayList("ip")).times(2); EasyMock.expect(autoScalingStrategy.terminate(EasyMock.>anyObject())).andReturn( - new AutoScalingData(Lists.newArrayList("ip"), Lists.newArrayList("ip")) + new AutoScalingData(Lists.newArrayList("ip")) ); EasyMock.replay(autoScalingStrategy); From f86342f7dc9cefdc74936707af9258e5e33342a5 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 19 Dec 2013 16:16:20 -0800 Subject: [PATCH 153/189] DbTaskStorage: Protect against invalid lock_payload --- .../io/druid/indexing/overlord/DbTaskStorage.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java b/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java index 5dc6e1c6fff..cf0fb4f3e24 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/DbTaskStorage.java @@ -489,7 +489,18 @@ public class DbTaskStorage implements TaskStorage final Map retMap = Maps.newHashMap(); for (final Map row : dbTaskLocks) { - retMap.put((Long) row.get("id"), jsonMapper.readValue((byte[]) row.get("lock_payload"), TaskLock.class)); + try { + retMap.put( + (Long) row.get("id"), + jsonMapper.readValue((byte[]) row.get("lock_payload"), TaskLock.class) + ); + } + catch (Exception e) { + log.makeAlert(e, "Failed to deserialize TaskLock") + .addData("task", taskid) + .addData("lockPayload", row) + .emit(); + } } return retMap; } From 0ff7f0e8e025ea92d954214ff7077b1f259703c4 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 19 Dec 2013 16:16:34 -0800 Subject: [PATCH 154/189] TaskActionToolbox: Combine adjacent ifs --- .../indexing/common/actions/TaskActionToolbox.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskActionToolbox.java b/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskActionToolbox.java index 10c4d627462..d9b0520f40b 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskActionToolbox.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/actions/TaskActionToolbox.java @@ -76,13 +76,9 @@ public class TaskActionToolbox Preconditions.checkArgument(!segments.isEmpty(), "segments nonempty"); final DataSegment firstSegment = segments.iterator().next(); for (final DataSegment segment : segments) { - if (!segment.getDataSource().equals(firstSegment.getDataSource())) { - return false; - } - if (!segment.getInterval().equals(firstSegment.getInterval())) { - return false; - } - if (!segment.getVersion().equals(firstSegment.getVersion())) { + if (!segment.getDataSource().equals(firstSegment.getDataSource()) + || !segment.getInterval().equals(firstSegment.getInterval()) + || !segment.getVersion().equals(firstSegment.getVersion())) { return false; } } From 3dd9a255469c6512526f8788fce8f2dc2e8523b7 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Thu, 19 Dec 2013 16:18:16 -0800 Subject: [PATCH 155/189] Fix import --- .../overlord/scaling/SimpleResourceManagementStrategy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java b/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java index 6b6fc613a7b..68733fed9cf 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java @@ -19,8 +19,8 @@ package io.druid.indexing.overlord.scaling; -import com.google.api.client.repackaged.com.google.common.base.Joiner; import com.google.common.base.Function; +import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.collect.Collections2; From 0ee6136ea3ca5481e951bafa7d594b365862d026 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 20 Dec 2013 08:56:17 -0800 Subject: [PATCH 156/189] NoopTask: Fix things that should be static. Add simple factory method. --- .../java/io/druid/indexing/common/task/NoopTask.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/NoopTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/NoopTask.java index d45f66377b7..b4de3512fbe 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/NoopTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/NoopTask.java @@ -35,9 +35,9 @@ import org.joda.time.DateTime; public class NoopTask extends AbstractTask { private static final Logger log = new Logger(NoopTask.class); - private static int defaultRunTime = 2500; - private static int defaultIsReadyTime = 0; - private static IsReadyResult defaultIsReadyResult = IsReadyResult.YES; + private static final int defaultRunTime = 2500; + private static final int defaultIsReadyTime = 0; + private static final IsReadyResult defaultIsReadyResult = IsReadyResult.YES; enum IsReadyResult { @@ -139,4 +139,9 @@ public class NoopTask extends AbstractTask log.info("Woke up!"); return TaskStatus.success(getId()); } + + public static NoopTask create() + { + return new NoopTask(null, 0, 0, null, null); + } } From 4a722c0a6d14cb172835c6f862d8c9b2c9053046 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 20 Dec 2013 08:59:35 -0800 Subject: [PATCH 157/189] Autoscaling changes from code review. - Log and return immediately when workerSetupData is null - Allow provisioning more nodes while other nodes are still provisioning - Add tests for bumping up the minimum version --- .../SimpleResourceManagementStrategy.java | 51 +++--- .../SimpleResourceManagementStrategyTest.java | 167 +++++++++++++++++- 2 files changed, 195 insertions(+), 23 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java b/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java index 68733fed9cf..4f541f113c7 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java @@ -90,6 +90,10 @@ public class SimpleResourceManagementStrategy implements ResourceManagementStrat synchronized (lock) { boolean didProvision = false; final WorkerSetupData workerSetupData = workerSetupDataRef.get(); + if (workerSetupData == null) { + log.warn("No workerSetupData available, cannot provision new workers."); + return false; + } final Predicate isValidWorker = createValidWorkerPredicate(workerSetupData); final int currValidWorkers = Collections2.filter(zkWorkers, isValidWorker).size(); @@ -112,21 +116,22 @@ public class SimpleResourceManagementStrategy implements ResourceManagementStrat updateTargetWorkerCount(pendingTasks, zkWorkers); - if (currentlyProvisioning.isEmpty()) { - int want = targetWorkerCount - (currValidWorkers + currentlyProvisioning.size()); - while (want > 0) { - final AutoScalingData provisioned = autoScalingStrategy.provision(); - if (provisioned == null) { - break; - } else { - currentlyProvisioning.addAll(provisioned.getNodeIds()); - lastProvisionTime = new DateTime(); - scalingStats.addProvisionEvent(provisioned); - want -= provisioned.getNodeIds().size(); - didProvision = true; - } + int want = targetWorkerCount - (currValidWorkers + currentlyProvisioning.size()); + while (want > 0) { + final AutoScalingData provisioned = autoScalingStrategy.provision(); + final List newNodes; + if (provisioned == null || (newNodes = provisioned.getNodeIds()).isEmpty()) { + break; + } else { + currentlyProvisioning.addAll(newNodes); + lastProvisionTime = new DateTime(); + scalingStats.addProvisionEvent(provisioned); + want -= provisioned.getNodeIds().size(); + didProvision = true; } - } else { + } + + if (!currentlyProvisioning.isEmpty()) { Duration durSinceLastProvision = new Duration(lastProvisionTime, new DateTime()); log.info("%s provisioning. Current wait time: %s", currentlyProvisioning, durSinceLastProvision); @@ -151,6 +156,11 @@ public class SimpleResourceManagementStrategy implements ResourceManagementStrat public boolean doTerminate(Collection pendingTasks, Collection zkWorkers) { synchronized (lock) { + if (workerSetupDataRef.get() == null) { + log.warn("No workerSetupData available, cannot terminate workers."); + return false; + } + boolean didTerminate = false; final Set workerNodeIds = Sets.newHashSet( autoScalingStrategy.ipToIdLookup( @@ -218,7 +228,7 @@ public class SimpleResourceManagementStrategy implements ResourceManagementStrat } else { Duration durSinceLastTerminate = new Duration(lastTerminateTime, new DateTime()); - log.info("%s terminating. Current wait time: ", currentlyTerminating, durSinceLastTerminate); + log.info("%s terminating. Current wait time: %s", currentlyTerminating, durSinceLastTerminate); if (durSinceLastTerminate.isLongerThan(config.getMaxScalingDuration().toStandardDuration())) { log.makeAlert("Worker node termination taking too long!") @@ -265,22 +275,23 @@ public class SimpleResourceManagementStrategy implements ResourceManagementStrat { synchronized (lock) { final WorkerSetupData workerSetupData = workerSetupDataRef.get(); + final Collection validWorkers = Collections2.filter( + zkWorkers, + createValidWorkerPredicate(workerSetupData) + ); if (targetWorkerCount < 0) { // Initialize to size of current worker pool targetWorkerCount = zkWorkers.size(); log.info( - "Starting with %,d workers (min = %,d, max = %,d).", + "Starting with a target of %,d workers (current = %,d, min = %,d, max = %,d).", targetWorkerCount, + validWorkers.size(), workerSetupData.getMinNumWorkers(), workerSetupData.getMaxNumWorkers() ); } - final Collection validWorkers = Collections2.filter( - zkWorkers, - createValidWorkerPredicate(workerSetupData) - ); final boolean atSteadyState = currentlyProvisioning.isEmpty() && currentlyTerminating.isEmpty() && validWorkers.size() == targetWorkerCount; diff --git a/indexing-service/src/test/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategyTest.java b/indexing-service/src/test/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategyTest.java index 10c7bf77882..6ffc6ae6222 100644 --- a/indexing-service/src/test/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategyTest.java +++ b/indexing-service/src/test/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategyTest.java @@ -19,6 +19,7 @@ package io.druid.indexing.overlord.scaling; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -28,6 +29,7 @@ import com.metamx.emitter.service.ServiceEventBuilder; import io.druid.common.guava.DSuppliers; import io.druid.indexing.common.TestMergeTask; import io.druid.indexing.common.TaskStatus; +import io.druid.indexing.common.task.NoopTask; import io.druid.indexing.common.task.Task; import io.druid.indexing.overlord.RemoteTaskRunnerWorkItem; import io.druid.indexing.overlord.ZkWorker; @@ -63,7 +65,7 @@ public class SimpleResourceManagementStrategyTest public void setUp() throws Exception { autoScalingStrategy = EasyMock.createMock(AutoScalingStrategy.class); - workerSetupData = new AtomicReference( + workerSetupData = new AtomicReference<>( new WorkerSetupData( "0", 0, 2, null, null, null ) @@ -309,15 +311,174 @@ public class SimpleResourceManagementStrategyTest EasyMock.verify(autoScalingStrategy); } + @Test + public void testNoActionNeeded() throws Exception + { + EasyMock.reset(autoScalingStrategy); + EasyMock.expect(autoScalingStrategy.ipToIdLookup(EasyMock.>anyObject())) + .andReturn(Lists.newArrayList("ip")); + EasyMock.replay(autoScalingStrategy); + + boolean terminatedSomething = simpleResourceManagementStrategy.doTerminate( + Arrays.asList( + new RemoteTaskRunnerWorkItem(testTask.getId(), null, null).withQueueInsertionTime(new DateTime()) + ), + Arrays.asList( + new TestZkWorker(NoopTask.create()), + new TestZkWorker(NoopTask.create()) + ) + ); + + Assert.assertFalse(terminatedSomething); + EasyMock.verify(autoScalingStrategy); + + EasyMock.reset(autoScalingStrategy); + EasyMock.expect(autoScalingStrategy.ipToIdLookup(EasyMock.>anyObject())) + .andReturn(Lists.newArrayList("ip")); + EasyMock.replay(autoScalingStrategy); + + boolean provisionedSomething = simpleResourceManagementStrategy.doProvision( + Arrays.asList( + new RemoteTaskRunnerWorkItem(testTask.getId(), null, null).withQueueInsertionTime(new DateTime()) + ), + Arrays.asList( + new TestZkWorker(NoopTask.create()), + new TestZkWorker(NoopTask.create()) + ) + ); + + Assert.assertFalse(provisionedSomething); + EasyMock.verify(autoScalingStrategy); + } + + @Test + public void testMinVersionIncrease() throws Exception + { + // Don't terminate anything + EasyMock.reset(autoScalingStrategy); + EasyMock.expect(autoScalingStrategy.ipToIdLookup(EasyMock.>anyObject())) + .andReturn(Lists.newArrayList("ip")); + EasyMock.replay(autoScalingStrategy); + boolean terminatedSomething = simpleResourceManagementStrategy.doTerminate( + Arrays.asList(), + Arrays.asList( + new TestZkWorker(NoopTask.create(), "h1", "i1", "0"), + new TestZkWorker(NoopTask.create(), "h1", "i2", "0") + ) + ); + Assert.assertFalse(terminatedSomething); + EasyMock.verify(autoScalingStrategy); + + // Don't provision anything + EasyMock.reset(autoScalingStrategy); + EasyMock.expect(autoScalingStrategy.ipToIdLookup(EasyMock.>anyObject())) + .andReturn(Lists.newArrayList("ip")); + EasyMock.replay(autoScalingStrategy); + boolean provisionedSomething = simpleResourceManagementStrategy.doProvision( + Arrays.asList(), + Arrays.asList( + new TestZkWorker(NoopTask.create()), + new TestZkWorker(NoopTask.create()) + ) + ); + Assert.assertFalse(provisionedSomething); + EasyMock.verify(autoScalingStrategy); + + // Increase minVersion + workerSetupData.set(new WorkerSetupData("1", 0, 2, null, null, null)); + + // Provision two new workers + EasyMock.reset(autoScalingStrategy); + EasyMock.expect(autoScalingStrategy.ipToIdLookup(EasyMock.>anyObject())) + .andReturn(Lists.newArrayList("ip")); + EasyMock.expect(autoScalingStrategy.provision()).andReturn( + new AutoScalingData(Lists.newArrayList("h3")) + ); + EasyMock.expect(autoScalingStrategy.provision()).andReturn( + new AutoScalingData(Lists.newArrayList("h4")) + ); + EasyMock.replay(autoScalingStrategy); + provisionedSomething = simpleResourceManagementStrategy.doProvision( + Arrays.asList(), + Arrays.asList( + new TestZkWorker(NoopTask.create(), "h1", "i1", "0"), + new TestZkWorker(NoopTask.create(), "h2", "i2", "0") + ) + ); + Assert.assertTrue(provisionedSomething); + EasyMock.verify(autoScalingStrategy); + + // Terminate old workers + EasyMock.reset(autoScalingStrategy); + EasyMock.expect(autoScalingStrategy.ipToIdLookup(ImmutableList.of("i1", "i2", "i3", "i4"))).andReturn( + ImmutableList.of("h1", "h2", "h3", "h4") + ); + EasyMock.expect(autoScalingStrategy.terminate(ImmutableList.of("i1", "i2"))).andReturn( + new AutoScalingData(ImmutableList.of("h1", "h2")) + ); + EasyMock.replay(autoScalingStrategy); + terminatedSomething = simpleResourceManagementStrategy.doTerminate( + Arrays.asList(), + Arrays.asList( + new TestZkWorker(null, "h1", "i1", "0"), + new TestZkWorker(null, "h2", "i2", "0"), + new TestZkWorker(NoopTask.create(), "h3", "i3", "1"), + new TestZkWorker(NoopTask.create(), "h4", "i4", "1") + ) + ); + Assert.assertTrue(terminatedSomething); + EasyMock.verify(autoScalingStrategy); + } + + @Test + public void testNullWorkerSetupData() throws Exception + { + workerSetupData.set(null); + EasyMock.replay(autoScalingStrategy); + + boolean terminatedSomething = simpleResourceManagementStrategy.doTerminate( + Arrays.asList( + new RemoteTaskRunnerWorkItem(testTask.getId(), null, null).withQueueInsertionTime(new DateTime()) + ), + Arrays.asList( + new TestZkWorker(null) + ) + ); + + boolean provisionedSomething = simpleResourceManagementStrategy.doProvision( + Arrays.asList( + new RemoteTaskRunnerWorkItem(testTask.getId(), null, null).withQueueInsertionTime(new DateTime()) + ), + Arrays.asList( + new TestZkWorker(null) + ) + ); + + Assert.assertFalse(terminatedSomething); + Assert.assertFalse(provisionedSomething); + + EasyMock.verify(autoScalingStrategy); + } + private static class TestZkWorker extends ZkWorker { private final Task testTask; - private TestZkWorker( + public TestZkWorker( Task testTask ) { - super(new Worker("host", "ip", 3, "version"), null, new DefaultObjectMapper()); + this(testTask, "host", "ip", "0"); + } + + public TestZkWorker( + Task testTask, + String host, + String ip, + String version + ) + { + super(new Worker(host, ip, 3, version), null, new DefaultObjectMapper()); this.testTask = testTask; } From 6224577ed12141c1542ac5ccfc202be4862fd48c Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 20 Dec 2013 10:01:32 -0800 Subject: [PATCH 158/189] Autoscaling: Terminate obsolete workers faster --- .../SimpleResourceManagementStrategy.java | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java b/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java index 4f541f113c7..80f7aef94c8 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java @@ -56,16 +56,6 @@ public class SimpleResourceManagementStrategy implements ResourceManagementStrat private final Object lock = new Object(); private final Set currentlyProvisioning = Sets.newHashSet(); private final Set currentlyTerminating = Sets.newHashSet(); - private final Predicate isLazyWorker = new Predicate() - { - @Override - public boolean apply(ZkWorker input) - { - return input.getRunningTasks().isEmpty() - && System.currentTimeMillis() - input.getLastCompletedTaskTime().getMillis() - >= config.getWorkerIdleTimeout().toStandardDuration().getMillis(); - } - }; private int targetWorkerCount = -1; private DateTime lastProvisionTime = new DateTime(); @@ -94,7 +84,7 @@ public class SimpleResourceManagementStrategy implements ResourceManagementStrat log.warn("No workerSetupData available, cannot provision new workers."); return false; } - final Predicate isValidWorker = createValidWorkerPredicate(workerSetupData); + final Predicate isValidWorker = createValidWorkerPredicate(config, workerSetupData); final int currValidWorkers = Collections2.filter(zkWorkers, isValidWorker).size(); final List workerNodeIds = autoScalingStrategy.ipToIdLookup( @@ -156,7 +146,8 @@ public class SimpleResourceManagementStrategy implements ResourceManagementStrat public boolean doTerminate(Collection pendingTasks, Collection zkWorkers) { synchronized (lock) { - if (workerSetupDataRef.get() == null) { + final WorkerSetupData workerSetupData = workerSetupDataRef.get(); + if (workerSetupData == null) { log.warn("No workerSetupData available, cannot terminate workers."); return false; } @@ -191,6 +182,7 @@ public class SimpleResourceManagementStrategy implements ResourceManagementStrat updateTargetWorkerCount(pendingTasks, zkWorkers); + final Predicate isLazyWorker = createLazyWorkerPredicate(config, workerSetupData); if (currentlyTerminating.isEmpty()) { final int want = zkWorkers.size() - targetWorkerCount; if (want > 0) { @@ -250,7 +242,29 @@ public class SimpleResourceManagementStrategy implements ResourceManagementStrat return scalingStats; } - private Predicate createValidWorkerPredicate(final WorkerSetupData workerSetupData) + private static Predicate createLazyWorkerPredicate( + final SimpleResourceManagementConfig config, + final WorkerSetupData workerSetupData + ) + { + final Predicate isValidWorker = createValidWorkerPredicate(config, workerSetupData); + + return new Predicate() + { + @Override + public boolean apply(ZkWorker worker) + { + final boolean itHasBeenAWhile = System.currentTimeMillis() - worker.getLastCompletedTaskTime().getMillis() + >= config.getWorkerIdleTimeout().toStandardDuration().getMillis(); + return worker.getRunningTasks().isEmpty() && (itHasBeenAWhile || !isValidWorker.apply(worker)); + } + }; + } + + private static Predicate createValidWorkerPredicate( + final SimpleResourceManagementConfig config, + final WorkerSetupData workerSetupData + ) { return new Predicate() { @@ -277,8 +291,9 @@ public class SimpleResourceManagementStrategy implements ResourceManagementStrat final WorkerSetupData workerSetupData = workerSetupDataRef.get(); final Collection validWorkers = Collections2.filter( zkWorkers, - createValidWorkerPredicate(workerSetupData) + createValidWorkerPredicate(config, workerSetupData) ); + final Predicate isLazyWorker = createLazyWorkerPredicate(config, workerSetupData); if (targetWorkerCount < 0) { // Initialize to size of current worker pool From 97095ee3dbad22fb5ab4fbc39624c8f0e88cda9a Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 20 Dec 2013 10:13:30 -0800 Subject: [PATCH 159/189] [maven-release-plugin] prepare release druid-0.6.43 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 5bade4d64d1..cff2dc10fc0 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43-SNAPSHOT + 0.6.43 diff --git a/common/pom.xml b/common/pom.xml index 7cdef58b97a..660cd2caa3e 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43-SNAPSHOT + 0.6.43 diff --git a/examples/pom.xml b/examples/pom.xml index eb880023fb2..f22364d88a1 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43-SNAPSHOT + 0.6.43 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 9085d8af3d2..63497f9bcc8 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43-SNAPSHOT + 0.6.43 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 7e68c822fb2..2644e8d3bf5 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43-SNAPSHOT + 0.6.43 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 8b0a1708f53..a3520136f96 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43-SNAPSHOT + 0.6.43 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 9295c043a24..1bf14b92e72 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43-SNAPSHOT + 0.6.43 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 9356a120ce6..156a4c3240d 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43-SNAPSHOT + 0.6.43 diff --git a/pom.xml b/pom.xml index f0b96500d4a..8156c5c3313 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.43-SNAPSHOT + 0.6.43 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.43 diff --git a/processing/pom.xml b/processing/pom.xml index d821fdd2572..74079cbd0b1 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43-SNAPSHOT + 0.6.43 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index eebd245923f..cfc0f8cd68a 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.43-SNAPSHOT + 0.6.43 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 817fc5307e6..d9fd9d547c1 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43-SNAPSHOT + 0.6.43 diff --git a/server/pom.xml b/server/pom.xml index c88b542ce21..1cfa0beb151 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43-SNAPSHOT + 0.6.43 diff --git a/services/pom.xml b/services/pom.xml index 1d9cf5a4257..f1dcc4dffda 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.43-SNAPSHOT + 0.6.43 From c503f5e9c591c810c4a3137ee84d2b7378e5f34a Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 20 Dec 2013 10:13:35 -0800 Subject: [PATCH 160/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index cff2dc10fc0..c8d01d914c7 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43 + 0.6.44-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index 660cd2caa3e..cc804ade0d1 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43 + 0.6.44-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index f22364d88a1..2c260de9b26 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43 + 0.6.44-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 63497f9bcc8..7a86fafd1dc 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43 + 0.6.44-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 2644e8d3bf5..35acca14246 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43 + 0.6.44-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index a3520136f96..352fc1943ae 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43 + 0.6.44-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 1bf14b92e72..441f92187f1 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43 + 0.6.44-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 156a4c3240d..713779e4c6b 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43 + 0.6.44-SNAPSHOT diff --git a/pom.xml b/pom.xml index 8156c5c3313..f7eacb0e7d9 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.43 + 0.6.44-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.43 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index 74079cbd0b1..a63c90f9670 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43 + 0.6.44-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index cfc0f8cd68a..a1d565571a9 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.43 + 0.6.44-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index d9fd9d547c1..657661c4acd 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43 + 0.6.44-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index 1cfa0beb151..cdffd9871fc 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.43 + 0.6.44-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index f1dcc4dffda..df239e1684d 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.43 + 0.6.44-SNAPSHOT From e5b8546d197785ed6fec975bd40f61b8c8b96736 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 20 Dec 2013 11:04:54 -0800 Subject: [PATCH 161/189] Autoscaling fixes. - Initial targetWorkerCount must be subject to pool size limits - Use consistent workerSetupData for the entire autoscaling run - Don't call terminate() when we have nothing to terminate - Terminate obsolete workers even faster --- .../SimpleResourceManagementStrategy.java | 50 +++++++++++-------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java b/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java index 80f7aef94c8..fe08fd648e1 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java @@ -104,7 +104,7 @@ public class SimpleResourceManagementStrategy implements ResourceManagementStrat ); currentlyProvisioning.removeAll(workerNodeIds); - updateTargetWorkerCount(pendingTasks, zkWorkers); + updateTargetWorkerCount(workerSetupData, pendingTasks, zkWorkers); int want = targetWorkerCount - (currValidWorkers + currentlyProvisioning.size()); while (want > 0) { @@ -180,16 +180,16 @@ public class SimpleResourceManagementStrategy implements ResourceManagementStrat currentlyTerminating.clear(); currentlyTerminating.addAll(stillExisting); - updateTargetWorkerCount(pendingTasks, zkWorkers); + updateTargetWorkerCount(workerSetupData, pendingTasks, zkWorkers); final Predicate isLazyWorker = createLazyWorkerPredicate(config, workerSetupData); if (currentlyTerminating.isEmpty()) { - final int want = zkWorkers.size() - targetWorkerCount; - if (want > 0) { + final int excessWorkers = (zkWorkers.size() + currentlyProvisioning.size()) - targetWorkerCount; + if (excessWorkers > 0) { final List laziestWorkerIps = FluentIterable.from(zkWorkers) .filter(isLazyWorker) - .limit(want) + .limit(excessWorkers) .transform( new Function() { @@ -202,19 +202,23 @@ public class SimpleResourceManagementStrategy implements ResourceManagementStrat ) .toList(); - log.info( - "Terminating %,d workers (wanted %,d): %s", - laziestWorkerIps.size(), - want, - Joiner.on(", ").join(laziestWorkerIps) - ); + if (laziestWorkerIps.isEmpty()) { + log.info("Wanted to terminate %,d workers, but couldn't find any lazy ones!"); + } else { + log.info( + "Terminating %,d workers (wanted %,d): %s", + laziestWorkerIps.size(), + excessWorkers, + Joiner.on(", ").join(laziestWorkerIps) + ); - final AutoScalingData terminated = autoScalingStrategy.terminate(laziestWorkerIps); - if (terminated != null) { - currentlyTerminating.addAll(terminated.getNodeIds()); - lastTerminateTime = new DateTime(); - scalingStats.addTerminateEvent(terminated); - didTerminate = true; + final AutoScalingData terminated = autoScalingStrategy.terminate(laziestWorkerIps); + if (terminated != null) { + currentlyTerminating.addAll(terminated.getNodeIds()); + lastTerminateTime = new DateTime(); + scalingStats.addTerminateEvent(terminated); + didTerminate = true; + } } } } else { @@ -283,12 +287,12 @@ public class SimpleResourceManagementStrategy implements ResourceManagementStrat } private void updateTargetWorkerCount( + final WorkerSetupData workerSetupData, final Collection pendingTasks, final Collection zkWorkers ) { synchronized (lock) { - final WorkerSetupData workerSetupData = workerSetupDataRef.get(); final Collection validWorkers = Collections2.filter( zkWorkers, createValidWorkerPredicate(config, workerSetupData) @@ -296,8 +300,14 @@ public class SimpleResourceManagementStrategy implements ResourceManagementStrat final Predicate isLazyWorker = createLazyWorkerPredicate(config, workerSetupData); if (targetWorkerCount < 0) { - // Initialize to size of current worker pool - targetWorkerCount = zkWorkers.size(); + // Initialize to size of current worker pool, subject to pool size limits + targetWorkerCount = Math.max( + Math.min( + zkWorkers.size(), + workerSetupData.getMaxNumWorkers() + ), + workerSetupData.getMinNumWorkers() + ); log.info( "Starting with a target of %,d workers (current = %,d, min = %,d, max = %,d).", targetWorkerCount, From 3b15f6a8340e7d72659997241284a1c9d4fc27c8 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 20 Dec 2013 11:07:57 -0800 Subject: [PATCH 162/189] [maven-release-plugin] prepare release druid-0.6.44 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index c8d01d914c7..02e5ee045d7 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44-SNAPSHOT + 0.6.44 diff --git a/common/pom.xml b/common/pom.xml index cc804ade0d1..868b62fadc1 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44-SNAPSHOT + 0.6.44 diff --git a/examples/pom.xml b/examples/pom.xml index 2c260de9b26..972135858b9 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44-SNAPSHOT + 0.6.44 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 7a86fafd1dc..eca98739a06 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44-SNAPSHOT + 0.6.44 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 35acca14246..a8ebe215cc5 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44-SNAPSHOT + 0.6.44 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 352fc1943ae..d06cb03442d 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44-SNAPSHOT + 0.6.44 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 441f92187f1..2ad3de37811 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44-SNAPSHOT + 0.6.44 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 713779e4c6b..edbb37bef12 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44-SNAPSHOT + 0.6.44 diff --git a/pom.xml b/pom.xml index f7eacb0e7d9..fac88736c8a 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.44-SNAPSHOT + 0.6.44 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.44 diff --git a/processing/pom.xml b/processing/pom.xml index a63c90f9670..fba8175bfa2 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44-SNAPSHOT + 0.6.44 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index a1d565571a9..fe2a22432c4 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.44-SNAPSHOT + 0.6.44 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 657661c4acd..5280c1e7281 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44-SNAPSHOT + 0.6.44 diff --git a/server/pom.xml b/server/pom.xml index cdffd9871fc..03fc1a1307b 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44-SNAPSHOT + 0.6.44 diff --git a/services/pom.xml b/services/pom.xml index df239e1684d..d099fe720f5 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.44-SNAPSHOT + 0.6.44 From 5711ac4aff5d2fc721a4ef247b3178d9bd3980b5 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 20 Dec 2013 11:08:02 -0800 Subject: [PATCH 163/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 02e5ee045d7..8b6073e811d 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44 + 0.6.45-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index 868b62fadc1..6583512c97e 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44 + 0.6.45-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index 972135858b9..f1c2b1c24ca 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44 + 0.6.45-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index eca98739a06..37f41e73261 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44 + 0.6.45-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index a8ebe215cc5..89fdfa4df4e 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44 + 0.6.45-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index d06cb03442d..fbdcf1822cd 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44 + 0.6.45-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 2ad3de37811..8c8141e0cd2 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44 + 0.6.45-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index edbb37bef12..657c7df3640 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44 + 0.6.45-SNAPSHOT diff --git a/pom.xml b/pom.xml index fac88736c8a..ab711e0783f 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.44 + 0.6.45-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.44 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index fba8175bfa2..d7facc2e3f9 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44 + 0.6.45-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index fe2a22432c4..1655311bf78 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.44 + 0.6.45-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 5280c1e7281..6678ca0b6f7 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44 + 0.6.45-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index 03fc1a1307b..3654decc6a2 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.44 + 0.6.45-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index d099fe720f5..151f533a92e 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.44 + 0.6.45-SNAPSHOT From 4bc8ff4d6a679b5a2b52685de6228f876d0ad431 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 20 Dec 2013 11:19:36 -0800 Subject: [PATCH 164/189] Update versions --- build.sh | 2 +- docs/content/Booting-a-production-cluster.md | 2 +- docs/content/Examples.md | 4 ++-- docs/content/Realtime.md | 2 +- docs/content/Tutorial:-A-First-Look-at-Druid.md | 4 ++-- docs/content/Tutorial:-Loading-Your-Data-Part-2.md | 2 +- docs/content/Tutorial:-The-Druid-Cluster.md | 6 +++--- docs/content/Tutorial:-Webstream.md | 4 ++-- docs/content/Twitter-Tutorial.textile | 2 +- examples/config/historical/runtime.properties | 2 +- examples/config/realtime/runtime.properties | 2 +- services/src/main/java/io/druid/cli/CliBroker.java | 2 +- services/src/main/java/io/druid/cli/CliCoordinator.java | 2 +- services/src/main/java/io/druid/cli/CliHadoopIndexer.java | 2 +- services/src/main/java/io/druid/cli/CliHistorical.java | 2 +- services/src/main/java/io/druid/cli/CliOverlord.java | 2 +- services/src/main/java/io/druid/cli/CliRealtime.java | 2 +- services/src/main/java/io/druid/cli/CliRealtimeExample.java | 2 +- 18 files changed, 23 insertions(+), 23 deletions(-) diff --git a/build.sh b/build.sh index 9b7478e592e..95ec936e1ef 100755 --- a/build.sh +++ b/build.sh @@ -30,4 +30,4 @@ echo "For examples, see: " echo " " ls -1 examples/*/*sh echo " " -echo "See also http://druid.io/docs/0.6.40" +echo "See also http://druid.io/docs/0.6.45" diff --git a/docs/content/Booting-a-production-cluster.md b/docs/content/Booting-a-production-cluster.md index bb00506700f..3d048ff6b1b 100644 --- a/docs/content/Booting-a-production-cluster.md +++ b/docs/content/Booting-a-production-cluster.md @@ -3,7 +3,7 @@ layout: doc_page --- # Booting a Single Node Cluster # -[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.40-bin.tar.gz). +[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.45-bin.tar.gz). The [ec2 run script](https://github.com/metamx/druid/blob/master/examples/bin/run_ec2.sh), run_ec2.sh, is located at 'examples/bin' if you have checked out the code, or at the root of the project if you've downloaded a tarball. The scripts rely on the [Amazon EC2 API Tools](http://aws.amazon.com/developertools/351), and you will need to set three environment variables: diff --git a/docs/content/Examples.md b/docs/content/Examples.md index 7aff89ea644..e94c11bcf0d 100644 --- a/docs/content/Examples.md +++ b/docs/content/Examples.md @@ -19,13 +19,13 @@ Clone Druid and build it: git clone https://github.com/metamx/druid.git druid cd druid git fetch --tags -git checkout druid-0.6.40 +git checkout druid-0.6.45 ./build.sh ``` ### Downloading the DSK (Druid Standalone Kit) -[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.40-bin.tar.gz) a stand-alone tarball and run it: +[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.45-bin.tar.gz) a stand-alone tarball and run it: ``` bash tar -xzf druid-services-0.X.X-bin.tar.gz diff --git a/docs/content/Realtime.md b/docs/content/Realtime.md index 980e1dfb773..978bf4121d4 100644 --- a/docs/content/Realtime.md +++ b/docs/content/Realtime.md @@ -27,7 +27,7 @@ druid.host=localhost druid.service=realtime druid.port=8083 -druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.40"] +druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.45"] druid.zk.service.host=localhost diff --git a/docs/content/Tutorial:-A-First-Look-at-Druid.md b/docs/content/Tutorial:-A-First-Look-at-Druid.md index 1b35c0979e2..0d4bb480947 100644 --- a/docs/content/Tutorial:-A-First-Look-at-Druid.md +++ b/docs/content/Tutorial:-A-First-Look-at-Druid.md @@ -49,7 +49,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu ### Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.40-bin.tar.gz). Download this file to a directory of your choosing. +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.45-bin.tar.gz). Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -60,7 +60,7 @@ tar -zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.40 +cd druid-services-0.6.45 ``` You should see a bunch of files: diff --git a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md index 50fef2985f6..5c6c665de11 100644 --- a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md +++ b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md @@ -44,7 +44,7 @@ With real-world data, we recommend having a message bus such as [Apache Kafka](h #### Setting up Kafka -[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.40/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. +[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.45/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. Instructions for booting a Zookeeper and then Kafka cluster are available [here](http://kafka.apache.org/07/quickstart.html). diff --git a/docs/content/Tutorial:-The-Druid-Cluster.md b/docs/content/Tutorial:-The-Druid-Cluster.md index 348dfd1df53..62cf2fbcbdd 100644 --- a/docs/content/Tutorial:-The-Druid-Cluster.md +++ b/docs/content/Tutorial:-The-Druid-Cluster.md @@ -13,7 +13,7 @@ In this tutorial, we will set up other types of Druid nodes as well as and exter If you followed the first tutorial, you should already have Druid downloaded. If not, let's go back and do that first. -You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.40-bin.tar.gz) +You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.45-bin.tar.gz) and untar the contents within by issuing: @@ -149,7 +149,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.40"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.45"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b @@ -238,7 +238,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.40","io.druid.extensions:druid-kafka-seven:0.6.40"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.45","io.druid.extensions:druid-kafka-seven:0.6.45"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/docs/content/Tutorial:-Webstream.md b/docs/content/Tutorial:-Webstream.md index a9605f2fea4..e3607d75503 100644 --- a/docs/content/Tutorial:-Webstream.md +++ b/docs/content/Tutorial:-Webstream.md @@ -37,7 +37,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.40-bin.tar.gz) +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.45-bin.tar.gz) Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -48,7 +48,7 @@ tar zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.40 +cd druid-services-0.6.45 ``` You should see a bunch of files: diff --git a/docs/content/Twitter-Tutorial.textile b/docs/content/Twitter-Tutorial.textile index c82ea981ec4..e66cd62ce34 100644 --- a/docs/content/Twitter-Tutorial.textile +++ b/docs/content/Twitter-Tutorial.textile @@ -9,7 +9,7 @@ There are two ways to setup Druid: download a tarball, or build it from source. h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.40-bin.tar.gz. +We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.45-bin.tar.gz. Download this bad boy to a directory of your choosing. You can extract the awesomeness within by issuing: diff --git a/examples/config/historical/runtime.properties b/examples/config/historical/runtime.properties index fa2593c63a1..2c7950d4112 100644 --- a/examples/config/historical/runtime.properties +++ b/examples/config/historical/runtime.properties @@ -4,7 +4,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.40"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.45"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b diff --git a/examples/config/realtime/runtime.properties b/examples/config/realtime/runtime.properties index eae65e057f9..0946d6753e1 100644 --- a/examples/config/realtime/runtime.properties +++ b/examples/config/realtime/runtime.properties @@ -4,7 +4,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.40","io.druid.extensions:druid-kafka-seven:0.6.40","io.druid.extensions:druid-rabbitmq:0.6.40"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.45","io.druid.extensions:druid-kafka-seven:0.6.45","io.druid.extensions:druid-rabbitmq:0.6.45"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/services/src/main/java/io/druid/cli/CliBroker.java b/services/src/main/java/io/druid/cli/CliBroker.java index 3a7c88d4426..2feda4e5e03 100644 --- a/services/src/main/java/io/druid/cli/CliBroker.java +++ b/services/src/main/java/io/druid/cli/CliBroker.java @@ -53,7 +53,7 @@ import java.util.List; */ @Command( name = "broker", - description = "Runs a broker node, see http://druid.io/docs/0.6.40/Broker.html for a description" + description = "Runs a broker node, see http://druid.io/docs/0.6.45/Broker.html for a description" ) public class CliBroker extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliCoordinator.java b/services/src/main/java/io/druid/cli/CliCoordinator.java index 486511b1104..3e6da7fbb42 100644 --- a/services/src/main/java/io/druid/cli/CliCoordinator.java +++ b/services/src/main/java/io/druid/cli/CliCoordinator.java @@ -60,7 +60,7 @@ import java.util.List; */ @Command( name = "coordinator", - description = "Runs the Coordinator, see http://druid.io/docs/0.6.40/Coordinator.html for a description." + description = "Runs the Coordinator, see http://druid.io/docs/0.6.45/Coordinator.html for a description." ) public class CliCoordinator extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java index 2cdc9706cef..b654e8de86b 100644 --- a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java +++ b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java @@ -41,7 +41,7 @@ import java.util.List; */ @Command( name = "hadoop", - description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.40/Batch-ingestion.html for a description." + description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.45/Batch-ingestion.html for a description." ) public class CliHadoopIndexer implements Runnable { diff --git a/services/src/main/java/io/druid/cli/CliHistorical.java b/services/src/main/java/io/druid/cli/CliHistorical.java index e9c5ece9889..d53ddbf0a89 100644 --- a/services/src/main/java/io/druid/cli/CliHistorical.java +++ b/services/src/main/java/io/druid/cli/CliHistorical.java @@ -42,7 +42,7 @@ import java.util.List; */ @Command( name = "historical", - description = "Runs a Historical node, see http://druid.io/docs/0.6.40/Historical.html for a description" + description = "Runs a Historical node, see http://druid.io/docs/0.6.45/Historical.html for a description" ) public class CliHistorical extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index aa68c14c6bb..47777163726 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -93,7 +93,7 @@ import java.util.List; */ @Command( name = "overlord", - description = "Runs an Overlord node, see http://druid.io/docs/0.6.40/Indexing-Service.html for a description" + description = "Runs an Overlord node, see http://druid.io/docs/0.6.45/Indexing-Service.html for a description" ) public class CliOverlord extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtime.java b/services/src/main/java/io/druid/cli/CliRealtime.java index 70baabbc3c2..ade1069c899 100644 --- a/services/src/main/java/io/druid/cli/CliRealtime.java +++ b/services/src/main/java/io/druid/cli/CliRealtime.java @@ -30,7 +30,7 @@ import java.util.List; */ @Command( name = "realtime", - description = "Runs a realtime node, see http://druid.io/docs/0.6.40/Realtime.html for a description" + description = "Runs a realtime node, see http://druid.io/docs/0.6.45/Realtime.html for a description" ) public class CliRealtime extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtimeExample.java b/services/src/main/java/io/druid/cli/CliRealtimeExample.java index f4b5c4ab5d8..e5cc2249bca 100644 --- a/services/src/main/java/io/druid/cli/CliRealtimeExample.java +++ b/services/src/main/java/io/druid/cli/CliRealtimeExample.java @@ -42,7 +42,7 @@ import java.util.concurrent.Executor; */ @Command( name = "realtime", - description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.40/Realtime.html for a description" + description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.45/Realtime.html for a description" ) public class CliRealtimeExample extends ServerRunnable { From 17ad4ee2f0f4561ae0d1c5f4dc81389a23e988e9 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 20 Dec 2013 11:23:28 -0800 Subject: [PATCH 165/189] Fix RemoteTaskRunnerTest --- .../src/test/java/io/druid/indexing/common/TestMergeTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexing-service/src/test/java/io/druid/indexing/common/TestMergeTask.java b/indexing-service/src/test/java/io/druid/indexing/common/TestMergeTask.java index 906e6e6c1e6..05f3118d3c9 100644 --- a/indexing-service/src/test/java/io/druid/indexing/common/TestMergeTask.java +++ b/indexing-service/src/test/java/io/druid/indexing/common/TestMergeTask.java @@ -44,7 +44,7 @@ public class TestMergeTask extends MergeTask Lists.newArrayList( new DataSegment( "dummyDs", - new Interval(new DateTime(), new DateTime()), + new Interval(new DateTime(), new DateTime().plus(1)), new DateTime().toString(), null, null, From 29c0fa1df985cc508894586fad1fc0cab9c164ef Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 20 Dec 2013 11:25:31 -0800 Subject: [PATCH 166/189] [maven-release-plugin] prepare release druid-0.6.45 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 8b6073e811d..602509d9f99 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45-SNAPSHOT + 0.6.45 diff --git a/common/pom.xml b/common/pom.xml index 6583512c97e..71e0ce7bc33 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45-SNAPSHOT + 0.6.45 diff --git a/examples/pom.xml b/examples/pom.xml index f1c2b1c24ca..bd7aa5fd319 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45-SNAPSHOT + 0.6.45 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 37f41e73261..ff252e38307 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45-SNAPSHOT + 0.6.45 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 89fdfa4df4e..b11508baef9 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45-SNAPSHOT + 0.6.45 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index fbdcf1822cd..9211f6307ca 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45-SNAPSHOT + 0.6.45 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 8c8141e0cd2..757102ba2a7 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45-SNAPSHOT + 0.6.45 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 657c7df3640..11730d348a9 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45-SNAPSHOT + 0.6.45 diff --git a/pom.xml b/pom.xml index ab711e0783f..0671b8ed865 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.45-SNAPSHOT + 0.6.45 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.45 diff --git a/processing/pom.xml b/processing/pom.xml index d7facc2e3f9..b2171bb5ccf 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45-SNAPSHOT + 0.6.45 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 1655311bf78..36b0708dae5 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.45-SNAPSHOT + 0.6.45 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 6678ca0b6f7..e4e9fba065e 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45-SNAPSHOT + 0.6.45 diff --git a/server/pom.xml b/server/pom.xml index 3654decc6a2..6065d97a15c 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45-SNAPSHOT + 0.6.45 diff --git a/services/pom.xml b/services/pom.xml index 151f533a92e..d464f50beb5 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.45-SNAPSHOT + 0.6.45 From 9043c211b2ef0ca0c7efeca56075ab9b09aa3352 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 20 Dec 2013 11:25:36 -0800 Subject: [PATCH 167/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 602509d9f99..9879e2b100f 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45 + 0.6.46-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index 71e0ce7bc33..9ca631cae9d 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45 + 0.6.46-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index bd7aa5fd319..fa55220a573 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45 + 0.6.46-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index ff252e38307..05bab3edf76 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45 + 0.6.46-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index b11508baef9..4adea254904 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45 + 0.6.46-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 9211f6307ca..1d828b6b005 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45 + 0.6.46-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 757102ba2a7..692121cdbec 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45 + 0.6.46-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 11730d348a9..084eea93d83 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45 + 0.6.46-SNAPSHOT diff --git a/pom.xml b/pom.xml index 0671b8ed865..c6450b7e428 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.45 + 0.6.46-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.45 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index b2171bb5ccf..be81434b96b 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45 + 0.6.46-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 36b0708dae5..9f3985b2336 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.45 + 0.6.46-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index e4e9fba065e..bfab2042108 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45 + 0.6.46-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index 6065d97a15c..8b9636b2fde 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.45 + 0.6.46-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index d464f50beb5..04f6f8fd63f 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.45 + 0.6.46-SNAPSHOT From 4d83837e88f6008cb040fef377a118c2432377e1 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 20 Dec 2013 11:37:16 -0800 Subject: [PATCH 168/189] RealtimeIndexTask: Clean up imports and comments --- .../io/druid/indexing/common/task/RealtimeIndexTask.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/RealtimeIndexTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/RealtimeIndexTask.java index 6ddc523140b..c9235a045ad 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/RealtimeIndexTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/RealtimeIndexTask.java @@ -24,7 +24,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import com.google.common.io.Closeables; import com.metamx.common.exception.FormattedException; import com.metamx.emitter.EmittingLogger; @@ -35,9 +34,7 @@ import io.druid.indexing.common.TaskLock; import io.druid.indexing.common.TaskStatus; import io.druid.indexing.common.TaskToolbox; import io.druid.indexing.common.actions.LockAcquireAction; -import io.druid.indexing.common.actions.LockListAction; import io.druid.indexing.common.actions.LockReleaseAction; -import io.druid.indexing.common.actions.SegmentInsertAction; import io.druid.indexing.common.actions.TaskActionClient; import io.druid.query.FinalizeResultsQueryRunner; import io.druid.query.Query; @@ -212,7 +209,7 @@ public class RealtimeIndexTask extends AbstractTask @Override public void announceSegment(final DataSegment segment) throws IOException { - // NOTE: Side effect: Calling announceSegment causes a lock to be acquired + // Side effect: Calling announceSegment causes a lock to be acquired toolbox.getTaskActionClient().submit(new LockAcquireAction(segment.getInterval())); toolbox.getSegmentAnnouncer().announceSegment(segment); } @@ -231,6 +228,7 @@ public class RealtimeIndexTask extends AbstractTask @Override public void announceSegments(Iterable segments) throws IOException { + // Side effect: Calling announceSegments causes locks to be acquired for (DataSegment segment : segments) { toolbox.getTaskActionClient().submit(new LockAcquireAction(segment.getInterval())); } @@ -263,7 +261,7 @@ public class RealtimeIndexTask extends AbstractTask public String getVersion(final Interval interval) { try { - // NOTE: Side effect: Calling getVersion causes a lock to be acquired + // Side effect: Calling getVersion causes a lock to be acquired final TaskLock myLock = toolbox.getTaskActionClient() .submit(new LockAcquireAction(interval)); From 837dee1934b7d94dc44154a82aec450507a27e88 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 20 Dec 2013 11:38:42 -0800 Subject: [PATCH 169/189] RealtimePlumberSchool: Alert and continue on any exception when abandoning segments --- .../druid/segment/realtime/plumber/RealtimePlumberSchool.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/io/druid/segment/realtime/plumber/RealtimePlumberSchool.java b/server/src/main/java/io/druid/segment/realtime/plumber/RealtimePlumberSchool.java index 35abfcaf866..9dc18257b68 100644 --- a/server/src/main/java/io/druid/segment/realtime/plumber/RealtimePlumberSchool.java +++ b/server/src/main/java/io/druid/segment/realtime/plumber/RealtimePlumberSchool.java @@ -712,7 +712,7 @@ public class RealtimePlumberSchool implements PlumberSchool handoffCondition.notifyAll(); } } - catch (IOException e) { + catch (Exception e) { log.makeAlert(e, "Unable to abandon old segment for dataSource[%s]", schema.getDataSource()) .addData("interval", sink.getInterval()) .emit(); From 26991b5a2a33bc91cefdf958ffa2973e27eeebb3 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 20 Dec 2013 12:05:42 -0800 Subject: [PATCH 170/189] Indexing service: Fix termination related log message --- .../overlord/scaling/SimpleResourceManagementStrategy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java b/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java index fe08fd648e1..10e084b3c9e 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/scaling/SimpleResourceManagementStrategy.java @@ -203,7 +203,7 @@ public class SimpleResourceManagementStrategy implements ResourceManagementStrat .toList(); if (laziestWorkerIps.isEmpty()) { - log.info("Wanted to terminate %,d workers, but couldn't find any lazy ones!"); + log.info("Wanted to terminate %,d workers, but couldn't find any lazy ones!", excessWorkers); } else { log.info( "Terminating %,d workers (wanted %,d): %s", From bbe1b82347fc8cf0c126a60d7e8b12673a27ba5a Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 20 Dec 2013 12:26:27 -0800 Subject: [PATCH 171/189] Update versions --- build.sh | 2 +- docs/content/Booting-a-production-cluster.md | 2 +- docs/content/Examples.md | 4 ++-- docs/content/Realtime.md | 2 +- docs/content/Tutorial:-A-First-Look-at-Druid.md | 4 ++-- docs/content/Tutorial:-Loading-Your-Data-Part-2.md | 2 +- docs/content/Tutorial:-The-Druid-Cluster.md | 6 +++--- docs/content/Tutorial:-Webstream.md | 4 ++-- docs/content/Twitter-Tutorial.textile | 2 +- examples/config/historical/runtime.properties | 2 +- examples/config/realtime/runtime.properties | 2 +- services/src/main/java/io/druid/cli/CliBroker.java | 2 +- services/src/main/java/io/druid/cli/CliCoordinator.java | 2 +- services/src/main/java/io/druid/cli/CliHadoopIndexer.java | 2 +- services/src/main/java/io/druid/cli/CliHistorical.java | 2 +- services/src/main/java/io/druid/cli/CliOverlord.java | 2 +- services/src/main/java/io/druid/cli/CliRealtime.java | 2 +- services/src/main/java/io/druid/cli/CliRealtimeExample.java | 2 +- 18 files changed, 23 insertions(+), 23 deletions(-) diff --git a/build.sh b/build.sh index 95ec936e1ef..50bcefbfe7b 100755 --- a/build.sh +++ b/build.sh @@ -30,4 +30,4 @@ echo "For examples, see: " echo " " ls -1 examples/*/*sh echo " " -echo "See also http://druid.io/docs/0.6.45" +echo "See also http://druid.io/docs/0.6.46" diff --git a/docs/content/Booting-a-production-cluster.md b/docs/content/Booting-a-production-cluster.md index 3d048ff6b1b..3f57ce13d1c 100644 --- a/docs/content/Booting-a-production-cluster.md +++ b/docs/content/Booting-a-production-cluster.md @@ -3,7 +3,7 @@ layout: doc_page --- # Booting a Single Node Cluster # -[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.45-bin.tar.gz). +[Loading Your Data](Tutorial%3A-Loading-Your-Data-Part-2.html) and [All About Queries](Tutorial%3A-All-About-Queries.html) contain recipes to boot a small druid cluster on localhost. Here we will boot a small cluster on EC2. You can checkout the code, or download a tarball from [here](http://static.druid.io/artifacts/druid-services-0.6.46-bin.tar.gz). The [ec2 run script](https://github.com/metamx/druid/blob/master/examples/bin/run_ec2.sh), run_ec2.sh, is located at 'examples/bin' if you have checked out the code, or at the root of the project if you've downloaded a tarball. The scripts rely on the [Amazon EC2 API Tools](http://aws.amazon.com/developertools/351), and you will need to set three environment variables: diff --git a/docs/content/Examples.md b/docs/content/Examples.md index e94c11bcf0d..f0771408c75 100644 --- a/docs/content/Examples.md +++ b/docs/content/Examples.md @@ -19,13 +19,13 @@ Clone Druid and build it: git clone https://github.com/metamx/druid.git druid cd druid git fetch --tags -git checkout druid-0.6.45 +git checkout druid-0.6.46 ./build.sh ``` ### Downloading the DSK (Druid Standalone Kit) -[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.45-bin.tar.gz) a stand-alone tarball and run it: +[Download](http://static.druid.io/artifacts/releases/druid-services-0.6.46-bin.tar.gz) a stand-alone tarball and run it: ``` bash tar -xzf druid-services-0.X.X-bin.tar.gz diff --git a/docs/content/Realtime.md b/docs/content/Realtime.md index 978bf4121d4..a6111a8734b 100644 --- a/docs/content/Realtime.md +++ b/docs/content/Realtime.md @@ -27,7 +27,7 @@ druid.host=localhost druid.service=realtime druid.port=8083 -druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.45"] +druid.extensions.coordinates=["io.druid.extensions:druid-kafka-seven:0.6.46"] druid.zk.service.host=localhost diff --git a/docs/content/Tutorial:-A-First-Look-at-Druid.md b/docs/content/Tutorial:-A-First-Look-at-Druid.md index 0d4bb480947..1f6f43ac97e 100644 --- a/docs/content/Tutorial:-A-First-Look-at-Druid.md +++ b/docs/content/Tutorial:-A-First-Look-at-Druid.md @@ -49,7 +49,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu ### Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.45-bin.tar.gz). Download this file to a directory of your choosing. +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.46-bin.tar.gz). Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -60,7 +60,7 @@ tar -zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.45 +cd druid-services-0.6.46 ``` You should see a bunch of files: diff --git a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md index 5c6c665de11..cffb288f5b3 100644 --- a/docs/content/Tutorial:-Loading-Your-Data-Part-2.md +++ b/docs/content/Tutorial:-Loading-Your-Data-Part-2.md @@ -44,7 +44,7 @@ With real-world data, we recommend having a message bus such as [Apache Kafka](h #### Setting up Kafka -[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.45/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. +[KafkaFirehoseFactory](https://github.com/metamx/druid/blob/druid-0.6.46/realtime/src/main/java/com/metamx/druid/realtime/firehose/KafkaFirehoseFactory.java) is how druid communicates with Kafka. Using this [Firehose](Firehose.html) with the right configuration, we can import data into Druid in real-time without writing any code. To load data to a real-time node via Kafka, we'll first need to initialize Zookeeper and Kafka, and then configure and initialize a [Realtime](Realtime.html) node. Instructions for booting a Zookeeper and then Kafka cluster are available [here](http://kafka.apache.org/07/quickstart.html). diff --git a/docs/content/Tutorial:-The-Druid-Cluster.md b/docs/content/Tutorial:-The-Druid-Cluster.md index 62cf2fbcbdd..b2f5b6975ec 100644 --- a/docs/content/Tutorial:-The-Druid-Cluster.md +++ b/docs/content/Tutorial:-The-Druid-Cluster.md @@ -13,7 +13,7 @@ In this tutorial, we will set up other types of Druid nodes as well as and exter If you followed the first tutorial, you should already have Druid downloaded. If not, let's go back and do that first. -You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.45-bin.tar.gz) +You can download the latest version of druid [here](http://static.druid.io/artifacts/releases/druid-services-0.6.46-bin.tar.gz) and untar the contents within by issuing: @@ -149,7 +149,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.45"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.46"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b @@ -238,7 +238,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.45","io.druid.extensions:druid-kafka-seven:0.6.45"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.46","io.druid.extensions:druid-kafka-seven:0.6.46"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/docs/content/Tutorial:-Webstream.md b/docs/content/Tutorial:-Webstream.md index e3607d75503..cdbbc5f7ee7 100644 --- a/docs/content/Tutorial:-Webstream.md +++ b/docs/content/Tutorial:-Webstream.md @@ -37,7 +37,7 @@ There are two ways to setup Druid: download a tarball, or [Build From Source](Bu h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.45-bin.tar.gz) +We've built a tarball that contains everything you'll need. You'll find it [here](http://static.druid.io/artifacts/releases/druid-services-0.6.46-bin.tar.gz) Download this file to a directory of your choosing. You can extract the awesomeness within by issuing: @@ -48,7 +48,7 @@ tar zxvf druid-services-*-bin.tar.gz Not too lost so far right? That's great! If you cd into the directory: ``` -cd druid-services-0.6.45 +cd druid-services-0.6.46 ``` You should see a bunch of files: diff --git a/docs/content/Twitter-Tutorial.textile b/docs/content/Twitter-Tutorial.textile index e66cd62ce34..68780006068 100644 --- a/docs/content/Twitter-Tutorial.textile +++ b/docs/content/Twitter-Tutorial.textile @@ -9,7 +9,7 @@ There are two ways to setup Druid: download a tarball, or build it from source. h3. Download a Tarball -We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.45-bin.tar.gz. +We've built a tarball that contains everything you'll need. You'll find it "here":http://static.druid.io/artifacts/releases/druid-services-0.6.46-bin.tar.gz. Download this bad boy to a directory of your choosing. You can extract the awesomeness within by issuing: diff --git a/examples/config/historical/runtime.properties b/examples/config/historical/runtime.properties index 2c7950d4112..8d9f1f35096 100644 --- a/examples/config/historical/runtime.properties +++ b/examples/config/historical/runtime.properties @@ -4,7 +4,7 @@ druid.port=8081 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.45"] +druid.extensions.coordinates=["io.druid.extensions:druid-s3-extensions:0.6.46"] # Dummy read only AWS account (used to download example data) druid.s3.secretKey=QyyfVZ7llSiRg6Qcrql1eEUG7buFpAK6T6engr1b diff --git a/examples/config/realtime/runtime.properties b/examples/config/realtime/runtime.properties index 0946d6753e1..6ebf2e5dcdf 100644 --- a/examples/config/realtime/runtime.properties +++ b/examples/config/realtime/runtime.properties @@ -4,7 +4,7 @@ druid.port=8083 druid.zk.service.host=localhost -druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.45","io.druid.extensions:druid-kafka-seven:0.6.45","io.druid.extensions:druid-rabbitmq:0.6.45"] +druid.extensions.coordinates=["io.druid.extensions:druid-examples:0.6.46","io.druid.extensions:druid-kafka-seven:0.6.46","io.druid.extensions:druid-rabbitmq:0.6.46"] # Change this config to db to hand off to the rest of the Druid cluster druid.publish.type=noop diff --git a/services/src/main/java/io/druid/cli/CliBroker.java b/services/src/main/java/io/druid/cli/CliBroker.java index 2feda4e5e03..ec37aefbe82 100644 --- a/services/src/main/java/io/druid/cli/CliBroker.java +++ b/services/src/main/java/io/druid/cli/CliBroker.java @@ -53,7 +53,7 @@ import java.util.List; */ @Command( name = "broker", - description = "Runs a broker node, see http://druid.io/docs/0.6.45/Broker.html for a description" + description = "Runs a broker node, see http://druid.io/docs/0.6.46/Broker.html for a description" ) public class CliBroker extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliCoordinator.java b/services/src/main/java/io/druid/cli/CliCoordinator.java index 3e6da7fbb42..27c3f8021ab 100644 --- a/services/src/main/java/io/druid/cli/CliCoordinator.java +++ b/services/src/main/java/io/druid/cli/CliCoordinator.java @@ -60,7 +60,7 @@ import java.util.List; */ @Command( name = "coordinator", - description = "Runs the Coordinator, see http://druid.io/docs/0.6.45/Coordinator.html for a description." + description = "Runs the Coordinator, see http://druid.io/docs/0.6.46/Coordinator.html for a description." ) public class CliCoordinator extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java index b654e8de86b..a667b967b30 100644 --- a/services/src/main/java/io/druid/cli/CliHadoopIndexer.java +++ b/services/src/main/java/io/druid/cli/CliHadoopIndexer.java @@ -41,7 +41,7 @@ import java.util.List; */ @Command( name = "hadoop", - description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.45/Batch-ingestion.html for a description." + description = "Runs the batch Hadoop Druid Indexer, see http://druid.io/docs/0.6.46/Batch-ingestion.html for a description." ) public class CliHadoopIndexer implements Runnable { diff --git a/services/src/main/java/io/druid/cli/CliHistorical.java b/services/src/main/java/io/druid/cli/CliHistorical.java index d53ddbf0a89..2be3c013820 100644 --- a/services/src/main/java/io/druid/cli/CliHistorical.java +++ b/services/src/main/java/io/druid/cli/CliHistorical.java @@ -42,7 +42,7 @@ import java.util.List; */ @Command( name = "historical", - description = "Runs a Historical node, see http://druid.io/docs/0.6.45/Historical.html for a description" + description = "Runs a Historical node, see http://druid.io/docs/0.6.46/Historical.html for a description" ) public class CliHistorical extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliOverlord.java b/services/src/main/java/io/druid/cli/CliOverlord.java index 47777163726..020178f7b33 100644 --- a/services/src/main/java/io/druid/cli/CliOverlord.java +++ b/services/src/main/java/io/druid/cli/CliOverlord.java @@ -93,7 +93,7 @@ import java.util.List; */ @Command( name = "overlord", - description = "Runs an Overlord node, see http://druid.io/docs/0.6.45/Indexing-Service.html for a description" + description = "Runs an Overlord node, see http://druid.io/docs/0.6.46/Indexing-Service.html for a description" ) public class CliOverlord extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtime.java b/services/src/main/java/io/druid/cli/CliRealtime.java index ade1069c899..bfc05eb748c 100644 --- a/services/src/main/java/io/druid/cli/CliRealtime.java +++ b/services/src/main/java/io/druid/cli/CliRealtime.java @@ -30,7 +30,7 @@ import java.util.List; */ @Command( name = "realtime", - description = "Runs a realtime node, see http://druid.io/docs/0.6.45/Realtime.html for a description" + description = "Runs a realtime node, see http://druid.io/docs/0.6.46/Realtime.html for a description" ) public class CliRealtime extends ServerRunnable { diff --git a/services/src/main/java/io/druid/cli/CliRealtimeExample.java b/services/src/main/java/io/druid/cli/CliRealtimeExample.java index e5cc2249bca..dd2605140c2 100644 --- a/services/src/main/java/io/druid/cli/CliRealtimeExample.java +++ b/services/src/main/java/io/druid/cli/CliRealtimeExample.java @@ -42,7 +42,7 @@ import java.util.concurrent.Executor; */ @Command( name = "realtime", - description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.45/Realtime.html for a description" + description = "Runs a standalone realtime node for examples, see http://druid.io/docs/0.6.46/Realtime.html for a description" ) public class CliRealtimeExample extends ServerRunnable { From bd58def933cabf6e3ae48d3b2eb3304e045a6e27 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 20 Dec 2013 12:28:49 -0800 Subject: [PATCH 172/189] [maven-release-plugin] prepare release druid-0.6.46 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 9879e2b100f..e66feb4a3a9 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46-SNAPSHOT + 0.6.46 diff --git a/common/pom.xml b/common/pom.xml index 9ca631cae9d..ba3d89c7f38 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46-SNAPSHOT + 0.6.46 diff --git a/examples/pom.xml b/examples/pom.xml index fa55220a573..28dc6af0e2c 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46-SNAPSHOT + 0.6.46 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 05bab3edf76..7e4b6b562b9 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46-SNAPSHOT + 0.6.46 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 4adea254904..c6f7a1ae1e4 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46-SNAPSHOT + 0.6.46 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 1d828b6b005..74be68f6991 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46-SNAPSHOT + 0.6.46 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 692121cdbec..a8cea059613 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46-SNAPSHOT + 0.6.46 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index 084eea93d83..f110c2828f4 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46-SNAPSHOT + 0.6.46 diff --git a/pom.xml b/pom.xml index c6450b7e428..250588c7809 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.46-SNAPSHOT + 0.6.46 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.46 diff --git a/processing/pom.xml b/processing/pom.xml index be81434b96b..3459c90532a 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46-SNAPSHOT + 0.6.46 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index 9f3985b2336..ec8fd567106 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.46-SNAPSHOT + 0.6.46 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index bfab2042108..c6402ee47eb 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46-SNAPSHOT + 0.6.46 diff --git a/server/pom.xml b/server/pom.xml index 8b9636b2fde..731d079aab9 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46-SNAPSHOT + 0.6.46 diff --git a/services/pom.xml b/services/pom.xml index 04f6f8fd63f..3cc43f3599f 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.46-SNAPSHOT + 0.6.46 From 668cf009a07a0318f32aa008db454b966e1a38a5 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Fri, 20 Dec 2013 12:28:54 -0800 Subject: [PATCH 173/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index e66feb4a3a9..57ad7d79e58 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46 + 0.6.47-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index ba3d89c7f38..6a481899484 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46 + 0.6.47-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index 28dc6af0e2c..0ec8f06142c 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46 + 0.6.47-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 7e4b6b562b9..3d6343c7183 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46 + 0.6.47-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index c6f7a1ae1e4..1562f24a5e5 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46 + 0.6.47-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 74be68f6991..78447097bbe 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46 + 0.6.47-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index a8cea059613..e378ffa75ef 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46 + 0.6.47-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index f110c2828f4..acc488d3c26 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46 + 0.6.47-SNAPSHOT diff --git a/pom.xml b/pom.xml index 250588c7809..fb0dd02b1bd 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.46 + 0.6.47-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.46 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index 3459c90532a..6344bb65d91 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46 + 0.6.47-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index ec8fd567106..bf9653b5fb1 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.46 + 0.6.47-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index c6402ee47eb..5880eea1657 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46 + 0.6.47-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index 731d079aab9..e58278a173a 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.46 + 0.6.47-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index 3cc43f3599f..4c462e95139 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.46 + 0.6.47-SNAPSHOT From 95d92915bf2f78122751f0f8ad57afc560909691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Fri, 27 Dec 2013 16:13:38 -0800 Subject: [PATCH 174/189] more tests for Initialization + dependency cleanup --- pom.xml | 2 +- processing/pom.xml | 2 - server/pom.xml | 58 ------- .../java/io/druid/server/StatusResource.java | 19 ++- .../initialization/InitializationTest.java | 152 ++++++++++++++++++ .../io/druid/server/StatusResourceTest.java | 52 +----- .../io.druid.initialization.DruidModule | 2 +- .../src/main/java/io/druid/cli/Version.java | 4 +- 8 files changed, 172 insertions(+), 119 deletions(-) create mode 100644 server/src/test/java/io/druid/initialization/InitializationTest.java diff --git a/pom.xml b/pom.xml index fb0dd02b1bd..1114e9238b1 100644 --- a/pom.xml +++ b/pom.xml @@ -409,7 +409,7 @@ junit junit - 4.8.1 + 4.11 test diff --git a/processing/pom.xml b/processing/pom.xml index 6344bb65d91..8e67e03671a 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -49,8 +49,6 @@ com.metamx emitter - - com.ning compress-lzf diff --git a/server/pom.xml b/server/pom.xml index e58278a173a..c79a29e38b3 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -37,24 +37,14 @@ druid-processing ${project.parent.version} - - - com.metamx - emitter - com.metamx http-client - - com.metamx - java-util - com.metamx server-metrics - commons-cli commons-cli @@ -63,22 +53,10 @@ commons-lang commons-lang - - commons-io - commons-io - com.amazonaws aws-java-sdk - - com.ning - compress-lzf - - - org.skife.config - config-magic - org.apache.curator curator-framework @@ -87,38 +65,14 @@ org.apache.curator curator-x-discovery - - it.uniroma3.mat - extendedset - - - com.google.guava - guava - - - com.google.inject - guice - - - com.fasterxml.jackson.core - jackson-core - com.fasterxml.jackson.jaxrs jackson-jaxrs-json-provider - - com.fasterxml.jackson.core - jackson-databind - com.fasterxml.jackson.dataformat jackson-dataformat-smile - - javax.inject - javax.inject - org.jdbi jdbi @@ -139,22 +93,10 @@ org.eclipse.jetty jetty-server - - joda-time - joda-time - com.google.code.findbugs jsr305 - - log4j - log4j - - - org.slf4j - slf4j-log4j12 - io.tesla.aether tesla-aether diff --git a/server/src/main/java/io/druid/server/StatusResource.java b/server/src/main/java/io/druid/server/StatusResource.java index 643d4d4fd74..44aa50a0ee7 100644 --- a/server/src/main/java/io/druid/server/StatusResource.java +++ b/server/src/main/java/io/druid/server/StatusResource.java @@ -28,8 +28,8 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import java.util.ArrayList; +import java.util.Collection; import java.util.List; -import java.util.Set; /** */ @@ -40,7 +40,7 @@ public class StatusResource @Produces("application/json") public Status doGet() { - return new Status(); + return new Status(Initialization.getLoadedModules(DruidModule.class)); } public static class Status @@ -49,13 +49,18 @@ public class StatusResource final List modules; final Memory memory; - public Status() + public Status(Collection modules) { - this.version = Status.class.getPackage().getImplementationVersion(); - this.modules = getExtensionVersions(); + this.version = getDruidVersion(); + this.modules = getExtensionVersions(modules); this.memory = new Memory(Runtime.getRuntime()); } + private String getDruidVersion() + { + return Status.class.getPackage().getImplementationVersion(); + } + @JsonProperty public String getVersion() { @@ -98,9 +103,8 @@ public class StatusResource * * @return map of extensions loaded with their respective implementation versions. */ - private List getExtensionVersions() + private List getExtensionVersions(Collection druidModules) { - final Set druidModules = Initialization.getLoadedModules(DruidModule.class); List moduleVersions = new ArrayList<>(); for (DruidModule module : druidModules) { String artifact = module.getClass().getPackage().getImplementationTitle(); @@ -110,7 +114,6 @@ public class StatusResource } return moduleVersions; } - } @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/server/src/test/java/io/druid/initialization/InitializationTest.java b/server/src/test/java/io/druid/initialization/InitializationTest.java new file mode 100644 index 00000000000..d1cb0afd0c5 --- /dev/null +++ b/server/src/test/java/io/druid/initialization/InitializationTest.java @@ -0,0 +1,152 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.initialization; + +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableList; +import com.google.inject.Binder; +import com.google.inject.Injector; +import io.druid.server.initialization.ExtensionsConfig; +import junit.framework.Assert; +import org.junit.After; +import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class InitializationTest +{ + private String oldService; + private String oldHost; + private String oldPort; + + @Before + public void messWithSystemProperties() + { + // required to test Initialization.makeInjectorWithModules + oldService = System.setProperty("druid.service", "test-service"); + oldHost = System.setProperty("druid.host", "test-host"); + oldPort = System.setProperty("druid.port", "8080"); + } + + @After + public void cleanup() + { + System.setProperty("druid.service", oldService == null ? "" : oldService); + System.setProperty("druid.host", oldHost == null ? "" : oldHost); + System.setProperty("druid.port", oldPort == null ? "" : oldPort); + } + + @Test + public void test01InitialModulesEmpty() throws Exception + { + Assert.assertEquals( + "Initial set of loaded modules must be empty", + 0, + Initialization.getLoadedModules(DruidModule.class).size() + ); + } + + @Test + public void test02MakeStartupInjector() throws Exception + { + Injector startupInjector = Initialization.makeStartupInjector(); + Assert.assertNotNull(startupInjector); + Assert.assertNotNull(startupInjector.getInstance(ObjectMapper.class)); + } + + @Test + public void test03ClassLoaderExtensionsLoading() + { + Injector startupInjector = Initialization.makeStartupInjector(); + + Function fnClassName = new Function() + { + @Nullable + @Override + public String apply(@Nullable DruidModule input) + { + return input.getClass().getCanonicalName(); + } + }; + + Assert.assertFalse( + "modules does not contain TestDruidModule", + Collections2.transform(Initialization.getLoadedModules(DruidModule.class), fnClassName) + .contains("io.druid.initialization.InitializationTest.TestDruidModule") + ); + + Collection modules = Initialization.getFromExtensions( + startupInjector.getInstance(ExtensionsConfig.class), + DruidModule.class + ); + + Assert.assertTrue( + "modules contains TestDruidModule", + Collections2.transform(modules, fnClassName) + .contains("io.druid.initialization.InitializationTest.TestDruidModule") + ); + } + + @Test + public void test04MakeInjectorWithModules() throws Exception + { + Injector startupInjector = Initialization.makeStartupInjector(); + Injector injector = Initialization.makeInjectorWithModules(startupInjector, ImmutableList.of()); + Assert.assertNotNull(injector); + } + + @Test + public void testGetLoadedModules() + { + + Set modules = Initialization.getLoadedModules(DruidModule.class); + + Set loadedModules = Initialization.getLoadedModules(DruidModule.class); + Assert.assertEquals("Set from loaded modules #1 should be same!", modules, loadedModules); + + Set loadedModules2 = Initialization.getLoadedModules(DruidModule.class); + Assert.assertEquals("Set from loaded modules #2 should be same!", modules, loadedModules2); + } + + public static class TestDruidModule implements DruidModule + { + @Override + public List getJacksonModules() + { + return ImmutableList.of(); + } + + @Override + public void configure(Binder binder) + { + // Do nothing + } + } +} diff --git a/server/src/test/java/io/druid/server/StatusResourceTest.java b/server/src/test/java/io/druid/server/StatusResourceTest.java index cff0fb4618e..9075f97ce81 100644 --- a/server/src/test/java/io/druid/server/StatusResourceTest.java +++ b/server/src/test/java/io/druid/server/StatusResourceTest.java @@ -19,19 +19,14 @@ package io.druid.server; -import com.fasterxml.jackson.databind.Module; import com.google.common.collect.ImmutableList; -import com.google.inject.Binder; -import com.google.inject.Injector; import io.druid.initialization.DruidModule; -import io.druid.initialization.Initialization; -import io.druid.server.initialization.ExtensionsConfig; +import io.druid.initialization.InitializationTest; import junit.framework.Assert; import org.junit.Test; import java.util.Collection; import java.util.List; -import java.util.Set; import static io.druid.server.StatusResource.ModuleVersion; @@ -39,29 +34,14 @@ import static io.druid.server.StatusResource.ModuleVersion; */ public class StatusResourceTest { - - private Collection loadTestModule() - { - Injector baseInjector = Initialization.makeStartupInjector(); - return Initialization.getFromExtensions(baseInjector.getInstance(ExtensionsConfig.class), DruidModule.class); - } - @Test public void testLoadedModules() { - final StatusResource resource = new StatusResource(); - List statusResourceModuleList; - statusResourceModuleList = resource.doGet().getModules(); - Assert.assertEquals( - "No Modules should be loaded currently! " + statusResourceModuleList, - statusResourceModuleList.size(), 0 - ); + Collection modules = ImmutableList.of((DruidModule)new InitializationTest.TestDruidModule()); + List statusResourceModuleList = new StatusResource.Status(modules).getModules(); - Collection modules = loadTestModule(); - statusResourceModuleList = resource.doGet().getModules(); - - Assert.assertEquals("Status should have all modules loaded!", statusResourceModuleList.size(), modules.size()); + Assert.assertEquals("Status should have all modules loaded!", modules.size(), statusResourceModuleList.size()); for (DruidModule module : modules) { String moduleName = module.getClass().getCanonicalName(); @@ -74,30 +54,6 @@ public class StatusResourceTest } Assert.assertTrue("Status resource should contain module " + moduleName, contains); } - - /* - * StatusResource only uses Initialization.getLoadedModules - */ - for (int i = 0; i < 5; i++) { - Set loadedModules = Initialization.getLoadedModules(DruidModule.class); - Assert.assertEquals("Set from loaded module should be same!", loadedModules, modules); - } } - - public static class TestDruidModule implements DruidModule - { - @Override - public List getJacksonModules() - { - return ImmutableList.of(); - } - - @Override - public void configure(Binder binder) - { - // Do nothing - } - } - } diff --git a/server/src/test/resources/META-INF/services/io.druid.initialization.DruidModule b/server/src/test/resources/META-INF/services/io.druid.initialization.DruidModule index 09678a839eb..b5bc03d5265 100644 --- a/server/src/test/resources/META-INF/services/io.druid.initialization.DruidModule +++ b/server/src/test/resources/META-INF/services/io.druid.initialization.DruidModule @@ -1 +1 @@ -io.druid.server.StatusResourceTest$TestDruidModule +io.druid.initialization.InitializationTest$TestDruidModule diff --git a/services/src/main/java/io/druid/cli/Version.java b/services/src/main/java/io/druid/cli/Version.java index 210591e81e6..45c258de803 100644 --- a/services/src/main/java/io/druid/cli/Version.java +++ b/services/src/main/java/io/druid/cli/Version.java @@ -20,6 +20,8 @@ package io.druid.cli; import io.airlift.command.Command; +import io.druid.initialization.DruidModule; +import io.druid.initialization.Initialization; import io.druid.server.StatusResource; @Command( @@ -31,6 +33,6 @@ public class Version implements Runnable @Override public void run() { - System.out.println(new StatusResource.Status()); + System.out.println(new StatusResource.Status(Initialization.getLoadedModules(DruidModule.class))); } } From ef05312c97da0848b0e03638a0d3baf5ee037593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Mon, 30 Dec 2013 15:26:46 -0800 Subject: [PATCH 175/189] cleanup dependencies --- common/pom.xml | 22 ---------- indexing-service/pom.xml | 91 ++-------------------------------------- pom.xml | 10 +++++ processing/pom.xml | 37 +++------------- server/pom.xml | 8 ++-- 5 files changed, 22 insertions(+), 146 deletions(-) diff --git a/common/pom.xml b/common/pom.xml index 6a481899484..b46929f30d2 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -59,14 +59,6 @@ org.skife.config config-magic - - org.apache.curator - curator-recipes - - - org.apache.curator - curator-x-discovery - org.hibernate hibernate-validator @@ -75,10 +67,6 @@ javax.validation validation-api - - it.uniroma3.mat - extendedset - com.google.guava guava @@ -127,16 +115,6 @@ log4j log4j - - mysql - mysql-connector-java - 5.1.18 - - - org.mozilla - rhino - 1.7R4 - diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 78447097bbe..82166796035 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -47,95 +47,10 @@ druid-indexing-hadoop ${project.parent.version} - - com.metamx - emitter - - - com.metamx - http-client - - - com.metamx - java-util - - - com.metamx - server-metrics - - - - commons-codec - commons-codec - - - commons-io - commons-io - - - org.skife.config - config-magic - - - org.apache.curator - curator-framework - - - org.apache.curator - curator-recipes - - - com.google.guava - guava - - - com.google.inject - guice - - - com.google.inject.extensions - guice-servlet - - - com.fasterxml.jackson.core - jackson-core - - - com.fasterxml.jackson.jaxrs - jackson-jaxrs-json-provider - - - com.fasterxml.jackson.core - jackson-databind - - - javax.inject - javax.inject - - - org.jdbi - jdbi - - - com.sun.jersey - jersey-core - - - com.sun.jersey.contribs - jersey-guice - - - org.eclipse.jetty - jetty-server - - - joda-time - joda-time - - - com.google.code.findbugs - jsr305 + mysql + mysql-connector-java + 5.1.18 diff --git a/pom.xml b/pom.xml index 1114e9238b1..2bae36d0f52 100644 --- a/pom.xml +++ b/pom.xml @@ -289,6 +289,16 @@ com.sun.jersey.contribs jersey-guice 1.17.1 + + + com.google.inject + guice + + + com.google.inject.extensions + guice-servlet + + com.sun.jersey diff --git a/processing/pom.xml b/processing/pom.xml index 8e67e03671a..21f35318569 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -37,10 +37,6 @@ druid-common ${project.parent.version} - - com.metamx - java-util - com.metamx bytebuffer-collections @@ -61,34 +57,6 @@ it.uniroma3.mat extendedset - - com.google.guava - guava - - - com.google.inject - guice - - - com.fasterxml.jackson.core - jackson-core - - - com.fasterxml.jackson.core - jackson-databind - - - javax.inject - javax.inject - - - joda-time - joda-time - - - log4j - log4j - org.slf4j slf4j-log4j12 @@ -105,6 +73,11 @@ com.ibm.icu icu4j + + org.mozilla + rhino + 1.7R4 + diff --git a/server/pom.xml b/server/pom.xml index c79a29e38b3..f41fba0b57f 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -53,6 +53,10 @@ commons-lang commons-lang + + javax.inject + javax.inject + com.amazonaws aws-java-sdk @@ -73,10 +77,6 @@ com.fasterxml.jackson.dataformat jackson-dataformat-smile - - org.jdbi - jdbi - com.sun.jersey jersey-server From c0e7837a586fd8624b407d6d58810dee7b74c88d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20L=C3=A9aut=C3=A9?= Date: Thu, 2 Jan 2014 10:18:31 -0800 Subject: [PATCH 176/189] add back guice-servlet --- server/pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/pom.xml b/server/pom.xml index f41fba0b57f..eda8a93fcb4 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -85,6 +85,10 @@ com.sun.jersey jersey-core + + com.google.inject.extensions + guice-servlet + com.sun.jersey.contribs jersey-guice From e4bcbcf3cc71d956a433c9fdad91866572659ae7 Mon Sep 17 00:00:00 2001 From: Jae Hyeon Bae Date: Thu, 2 Jan 2014 10:35:09 -0800 Subject: [PATCH 177/189] curator 2.3.0 --- pom.xml | 2 +- .../curator/discovery/DiscoveryModule.java | 22 ++++++++++--------- .../discovery/ServerDiscoveryFactory.java | 5 +++++ 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index fb0dd02b1bd..66ab8a1daf7 100644 --- a/pom.xml +++ b/pom.xml @@ -40,7 +40,7 @@ UTF-8 0.25.1 - 2.1.0-incubating + 2.3.0 0.1.7 diff --git a/server/src/main/java/io/druid/curator/discovery/DiscoveryModule.java b/server/src/main/java/io/druid/curator/discovery/DiscoveryModule.java index f3fc56d59a7..bc1558f60f6 100644 --- a/server/src/main/java/io/druid/curator/discovery/DiscoveryModule.java +++ b/server/src/main/java/io/druid/curator/discovery/DiscoveryModule.java @@ -40,14 +40,7 @@ import io.druid.guice.annotations.Self; import io.druid.server.DruidNode; import io.druid.server.initialization.CuratorDiscoveryConfig; import org.apache.curator.framework.CuratorFramework; -import org.apache.curator.x.discovery.ProviderStrategy; -import org.apache.curator.x.discovery.ServiceCache; -import org.apache.curator.x.discovery.ServiceCacheBuilder; -import org.apache.curator.x.discovery.ServiceDiscovery; -import org.apache.curator.x.discovery.ServiceDiscoveryBuilder; -import org.apache.curator.x.discovery.ServiceInstance; -import org.apache.curator.x.discovery.ServiceProvider; -import org.apache.curator.x.discovery.ServiceProviderBuilder; +import org.apache.curator.x.discovery.*; import org.apache.curator.x.discovery.details.ServiceCacheListener; import java.io.IOException; @@ -389,8 +382,12 @@ public class DiscoveryModule implements Module } @Override - public ServiceProviderBuilder refreshPaddingMs(int refreshPaddingMs) - { + public ServiceProviderBuilder downInstancePolicy(DownInstancePolicy downInstancePolicy) { + return this; + } + + @Override + public ServiceProviderBuilder additionalFilter(InstanceFilter tInstanceFilter) { return this; } } @@ -409,6 +406,11 @@ public class DiscoveryModule implements Module return null; } + @Override + public void noteError(ServiceInstance tServiceInstance) { + + } + @Override public void close() throws IOException { diff --git a/server/src/main/java/io/druid/curator/discovery/ServerDiscoveryFactory.java b/server/src/main/java/io/druid/curator/discovery/ServerDiscoveryFactory.java index c436289e70e..0e66df0b9ed 100644 --- a/server/src/main/java/io/druid/curator/discovery/ServerDiscoveryFactory.java +++ b/server/src/main/java/io/druid/curator/discovery/ServerDiscoveryFactory.java @@ -62,6 +62,11 @@ public class ServerDiscoveryFactory return null; } + @Override + public void noteError(ServiceInstance tServiceInstance) { + // do nothing + } + @Override public void close() throws IOException { From 7a0f634b02ffaba922125af1500317815bfca26f Mon Sep 17 00:00:00 2001 From: fjy Date: Thu, 2 Jan 2014 19:53:35 -0800 Subject: [PATCH 178/189] [maven-release-plugin] prepare release druid-0.6.47 --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 57ad7d79e58..11df3044402 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47-SNAPSHOT + 0.6.47 diff --git a/common/pom.xml b/common/pom.xml index b46929f30d2..f34e0e0c4f9 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47-SNAPSHOT + 0.6.47 diff --git a/examples/pom.xml b/examples/pom.xml index 0ec8f06142c..e50f20c7bc8 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47-SNAPSHOT + 0.6.47 diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 3d6343c7183..5a166243dbe 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47-SNAPSHOT + 0.6.47 diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 1562f24a5e5..12809f18fa7 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47-SNAPSHOT + 0.6.47 diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 82166796035..9d7b00c4b09 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47-SNAPSHOT + 0.6.47 diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index e378ffa75ef..32fd007f85d 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47-SNAPSHOT + 0.6.47 diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index acc488d3c26..b8dc53b56b6 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47-SNAPSHOT + 0.6.47 diff --git a/pom.xml b/pom.xml index 2756303acef..8797b20e38f 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.47-SNAPSHOT + 0.6.47 druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - ${project.artifactId}-${project.version} + druid-0.6.47 diff --git a/processing/pom.xml b/processing/pom.xml index 21f35318569..72abbea3bda 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47-SNAPSHOT + 0.6.47 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index bf9653b5fb1..ea1662429a2 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.47-SNAPSHOT + 0.6.47 diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 5880eea1657..7160faecf6f 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47-SNAPSHOT + 0.6.47 diff --git a/server/pom.xml b/server/pom.xml index eda8a93fcb4..9bcbf4947a3 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47-SNAPSHOT + 0.6.47 diff --git a/services/pom.xml b/services/pom.xml index 4c462e95139..af2b14433fe 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.47-SNAPSHOT + 0.6.47 From 2b46c1d29206e54dd40a1da9ce44b2c90898509e Mon Sep 17 00:00:00 2001 From: fjy Date: Thu, 2 Jan 2014 19:53:41 -0800 Subject: [PATCH 179/189] [maven-release-plugin] prepare for next development iteration --- cassandra-storage/pom.xml | 2 +- common/pom.xml | 2 +- examples/pom.xml | 2 +- hdfs-storage/pom.xml | 2 +- indexing-hadoop/pom.xml | 2 +- indexing-service/pom.xml | 2 +- kafka-eight/pom.xml | 2 +- kafka-seven/pom.xml | 2 +- pom.xml | 4 ++-- processing/pom.xml | 2 +- rabbitmq/pom.xml | 2 +- s3-extensions/pom.xml | 2 +- server/pom.xml | 2 +- services/pom.xml | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cassandra-storage/pom.xml b/cassandra-storage/pom.xml index 11df3044402..c56617fc659 100644 --- a/cassandra-storage/pom.xml +++ b/cassandra-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47 + 0.6.48-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index f34e0e0c4f9..9b732f714ee 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47 + 0.6.48-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index e50f20c7bc8..0e4f56ca1e8 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47 + 0.6.48-SNAPSHOT diff --git a/hdfs-storage/pom.xml b/hdfs-storage/pom.xml index 5a166243dbe..a59524271d5 100644 --- a/hdfs-storage/pom.xml +++ b/hdfs-storage/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47 + 0.6.48-SNAPSHOT diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 12809f18fa7..38d032fadba 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47 + 0.6.48-SNAPSHOT diff --git a/indexing-service/pom.xml b/indexing-service/pom.xml index 9d7b00c4b09..e8cfbdf8fa1 100644 --- a/indexing-service/pom.xml +++ b/indexing-service/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47 + 0.6.48-SNAPSHOT diff --git a/kafka-eight/pom.xml b/kafka-eight/pom.xml index 32fd007f85d..79d053ad355 100644 --- a/kafka-eight/pom.xml +++ b/kafka-eight/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47 + 0.6.48-SNAPSHOT diff --git a/kafka-seven/pom.xml b/kafka-seven/pom.xml index b8dc53b56b6..e2922e97e3c 100644 --- a/kafka-seven/pom.xml +++ b/kafka-seven/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47 + 0.6.48-SNAPSHOT diff --git a/pom.xml b/pom.xml index 8797b20e38f..3d082a9cf93 100644 --- a/pom.xml +++ b/pom.xml @@ -23,14 +23,14 @@ io.druid druid pom - 0.6.47 + 0.6.48-SNAPSHOT druid druid scm:git:ssh://git@github.com/metamx/druid.git scm:git:ssh://git@github.com/metamx/druid.git http://www.github.com/metamx/druid - druid-0.6.47 + ${project.artifactId}-${project.version} diff --git a/processing/pom.xml b/processing/pom.xml index 72abbea3bda..7b5cd091eb1 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47 + 0.6.48-SNAPSHOT diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml index ea1662429a2..e72c0aa9100 100644 --- a/rabbitmq/pom.xml +++ b/rabbitmq/pom.xml @@ -9,7 +9,7 @@ io.druid druid - 0.6.47 + 0.6.48-SNAPSHOT diff --git a/s3-extensions/pom.xml b/s3-extensions/pom.xml index 7160faecf6f..11c189ad9b2 100644 --- a/s3-extensions/pom.xml +++ b/s3-extensions/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47 + 0.6.48-SNAPSHOT diff --git a/server/pom.xml b/server/pom.xml index 9bcbf4947a3..a354a0728ed 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -28,7 +28,7 @@ io.druid druid - 0.6.47 + 0.6.48-SNAPSHOT diff --git a/services/pom.xml b/services/pom.xml index af2b14433fe..fba4cc40f45 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -27,7 +27,7 @@ io.druid druid - 0.6.47 + 0.6.48-SNAPSHOT From 3544b7ec4ae1e1a9d914fc6da25ce91231377c65 Mon Sep 17 00:00:00 2001 From: fjy Date: Mon, 6 Jan 2014 09:31:04 -0800 Subject: [PATCH 180/189] Fixes #346 --- .../java/io/druid/segment/realtime/RealtimeMetricsMonitor.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/main/java/io/druid/segment/realtime/RealtimeMetricsMonitor.java b/server/src/main/java/io/druid/segment/realtime/RealtimeMetricsMonitor.java index 246654ffd8f..5714e98cf62 100644 --- a/server/src/main/java/io/druid/segment/realtime/RealtimeMetricsMonitor.java +++ b/server/src/main/java/io/druid/segment/realtime/RealtimeMetricsMonitor.java @@ -20,6 +20,7 @@ package io.druid.segment.realtime; import com.google.common.collect.Maps; +import com.google.inject.Inject; import com.metamx.emitter.service.ServiceEmitter; import com.metamx.emitter.service.ServiceMetricEvent; import com.metamx.metrics.AbstractMonitor; @@ -34,6 +35,7 @@ public class RealtimeMetricsMonitor extends AbstractMonitor private final Map previousValues; private final List fireDepartments; + @Inject public RealtimeMetricsMonitor(List fireDepartments) { this.fireDepartments = fireDepartments; From bf158102c41dc134a16c5c6cbcdc51923759e49c Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Tue, 7 Jan 2014 07:17:19 -0800 Subject: [PATCH 181/189] IndexTask: Print metrics even if finishJob fails --- .../druid/indexing/common/task/IndexTask.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java index 6e09e46373c..f4265ca1d1f 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java @@ -368,18 +368,21 @@ public class IndexTask extends AbstractFixedIntervalTask } plumber.persist(firehose.commit()); - plumber.finishJob(); - // Output metrics - log.info( - "Task[%s] took in %,d rows (%,d processed, %,d unparseable, %,d thrown away) and output %,d rows", - getId(), - metrics.processed() + metrics.unparseable() + metrics.thrownAway(), - metrics.processed(), - metrics.unparseable(), - metrics.thrownAway(), - metrics.rowOutput() - ); + try { + plumber.finishJob(); + } + finally { + log.info( + "Task[%s] took in %,d rows (%,d processed, %,d unparseable, %,d thrown away) and output %,d rows", + getId(), + metrics.processed() + metrics.unparseable() + metrics.thrownAway(), + metrics.processed(), + metrics.unparseable(), + metrics.thrownAway(), + metrics.rowOutput() + ); + } // We expect a single segment to have been created. return Iterables.getOnlyElement(pushedSegments); From 83b4641e31e483f27102576983c0ea76de3576cf Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Tue, 7 Jan 2014 16:12:24 -0800 Subject: [PATCH 182/189] ForkingTaskRunnerConfig: Add java.io.tmpdir to allowedPrefixes --- .../indexing/overlord/config/ForkingTaskRunnerConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/config/ForkingTaskRunnerConfig.java b/indexing-service/src/main/java/io/druid/indexing/overlord/config/ForkingTaskRunnerConfig.java index e2f30e4235b..de864d544f4 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/config/ForkingTaskRunnerConfig.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/config/ForkingTaskRunnerConfig.java @@ -62,7 +62,8 @@ public class ForkingTaskRunnerConfig "druid", "io.druid", "user.timezone", - "file.encoding" + "file.encoding", + "java.io.tmpdir" ); public String getTaskDir() From 7f430d9fdef013ad494b393159bd251c48727866 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 8 Jan 2014 14:45:35 -0800 Subject: [PATCH 183/189] RealtimeIndexTask: If a Throwable was thrown it is not a normalExit --- .../io/druid/indexing/common/task/RealtimeIndexTask.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/RealtimeIndexTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/RealtimeIndexTask.java index c9235a045ad..01fa6e69149 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/RealtimeIndexTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/RealtimeIndexTask.java @@ -341,11 +341,11 @@ public class RealtimeIndexTask extends AbstractTask } } } - catch (Exception e) { + catch (Throwable e) { + normalExit = false; log.makeAlert(e, "Exception aborted realtime processing[%s]", schema.getDataSource()) .emit(); - normalExit = false; - throw Throwables.propagate(e); + throw e; } finally { if (normalExit) { From 2c53af4d663683c8096334169e29c455c8cca16f Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 8 Jan 2014 14:46:18 -0800 Subject: [PATCH 184/189] ForkingTaskRunner: Upload task logs even when job fails --- .../indexing/overlord/ForkingTaskRunner.java | 37 +++++++------------ 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/overlord/ForkingTaskRunner.java b/indexing-service/src/main/java/io/druid/indexing/overlord/ForkingTaskRunner.java index 1099bddbcc6..2be8a6a6a3b 100644 --- a/indexing-service/src/main/java/io/druid/indexing/overlord/ForkingTaskRunner.java +++ b/indexing-service/src/main/java/io/druid/indexing/overlord/ForkingTaskRunner.java @@ -218,29 +218,20 @@ public class ForkingTaskRunner implements TaskRunner, TaskLogStreamer } log.info("Logging task %s output to: %s", task.getId(), logFile); - - final InputStream fromProc = processHolder.process.getInputStream(); - final OutputStream toLogfile = closer.register( - Files.newOutputStreamSupplier(logFile).getOutput() - ); - boolean runFailed = true; - ByteStreams.copy(fromProc, toLogfile); - final int statusCode = processHolder.process.waitFor(); - log.info("Process exited with status[%d] for task: %s", statusCode, task.getId()); - - if (statusCode == 0) { - runFailed = false; + try (final OutputStream toLogfile = Files.newOutputStreamSupplier(logFile).getOutput()) { + ByteStreams.copy(processHolder.process.getInputStream(), toLogfile); + final int statusCode = processHolder.process.waitFor(); + log.info("Process exited with status[%d] for task: %s", statusCode, task.getId()); + if (statusCode == 0) { + runFailed = false; + } + } + finally { + // Upload task logs + taskLogPusher.pushTaskLog(task.getId(), logFile); } - - // Upload task logs - - // XXX: Consider uploading periodically for very long-lived tasks to prevent - // XXX: bottlenecks at the end or the possibility of losing a lot of logs all - // XXX: at once. - - taskLogPusher.pushTaskLog(task.getId(), logFile); if (!runFailed) { // Process exited successfully @@ -255,9 +246,9 @@ public class ForkingTaskRunner implements TaskRunner, TaskLogStreamer closer.close(); } } - catch (Exception e) { - log.info(e, "Exception caught during execution"); - throw Throwables.propagate(e); + catch (Throwable t) { + log.info(t, "Exception caught during execution"); + throw Throwables.propagate(t); } finally { try { From 1476d7f51e08a3a338cc675b8fbc17dd677117a7 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 8 Jan 2014 15:21:53 -0800 Subject: [PATCH 185/189] LinearShardSpec, NumberedShardSpec toString methods --- .../io/druid/timeline/partition/LinearShardSpec.java | 10 ++++++++-- .../druid/timeline/partition/NumberedShardSpec.java | 11 +++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/io/druid/timeline/partition/LinearShardSpec.java b/server/src/main/java/io/druid/timeline/partition/LinearShardSpec.java index 5b716b20a73..ea7d3256229 100644 --- a/server/src/main/java/io/druid/timeline/partition/LinearShardSpec.java +++ b/server/src/main/java/io/druid/timeline/partition/LinearShardSpec.java @@ -24,8 +24,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Preconditions; import io.druid.data.input.InputRow; -import java.util.Map; - public class LinearShardSpec implements ShardSpec { private int partitionNum; @@ -53,4 +51,12 @@ public class LinearShardSpec implements ShardSpec public boolean isInChunk(InputRow inputRow) { return true; } + + @Override + public String toString() + { + return "LinearShardSpec{" + + "partitionNum=" + partitionNum + + '}'; + } } diff --git a/server/src/main/java/io/druid/timeline/partition/NumberedShardSpec.java b/server/src/main/java/io/druid/timeline/partition/NumberedShardSpec.java index 7c09787b5cc..73a3437a80a 100644 --- a/server/src/main/java/io/druid/timeline/partition/NumberedShardSpec.java +++ b/server/src/main/java/io/druid/timeline/partition/NumberedShardSpec.java @@ -25,8 +25,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Preconditions; import io.druid.data.input.InputRow; -import java.util.Map; - public class NumberedShardSpec implements ShardSpec { @JsonIgnore @@ -71,4 +69,13 @@ public class NumberedShardSpec implements ShardSpec { return true; } + + @Override + public String toString() + { + return "NumberedShardSpec{" + + "partitionNum=" + partitionNum + + ", partitions=" + partitions + + '}'; + } } From 9037141c00b17753eddb969c6f3b8857b00771c4 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Wed, 8 Jan 2014 15:22:07 -0800 Subject: [PATCH 186/189] IndexTask: Better logging at the end of each segment --- .../main/java/io/druid/indexing/common/task/IndexTask.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java b/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java index f4265ca1d1f..25603c981cd 100644 --- a/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java +++ b/indexing-service/src/main/java/io/druid/indexing/common/task/IndexTask.java @@ -374,8 +374,11 @@ public class IndexTask extends AbstractFixedIntervalTask } finally { log.info( - "Task[%s] took in %,d rows (%,d processed, %,d unparseable, %,d thrown away) and output %,d rows", + "Task[%s] interval[%s] partition[%d] took in %,d rows (%,d processed, %,d unparseable, %,d thrown away)" + + " and output %,d rows", getId(), + interval, + schema.getShardSpec().getPartitionNum(), metrics.processed() + metrics.unparseable() + metrics.thrownAway(), metrics.processed(), metrics.unparseable(), From 50a6839693a9acc48405d29f9a6c92e29cb29c12 Mon Sep 17 00:00:00 2001 From: fjy Date: Wed, 8 Jan 2014 15:51:03 -0800 Subject: [PATCH 187/189] inital commit of topNs --- docs/content/TopNMetricSpec.md | 45 + docs/content/TopNQuery.md | 119 ++ docs/content/toc.textile | 2 + .../src/main/java/io/druid/query/Query.java | 5 +- .../AggregateTopNMetricFirstAlgorithm.java | 174 +++ .../druid/query/topn/BaseTopNAlgorithm.java | 234 ++++ .../query/topn/BySegmentTopNResultValue.java | 90 ++ .../topn/DimExtractionTopNAlgorithm.java | 169 +++ .../io/druid/query/topn/DimValHolder.java | 110 ++ .../DimensionAndMetricValueExtractor.java | 83 ++ .../query/topn/InvertedTopNMetricSpec.java | 104 ++ .../query/topn/LegacyTopNMetricSpec.java | 51 + .../topn/LexicographicTopNMetricSpec.java | 121 ++ .../query/topn/NumericTopNMetricSpec.java | 160 +++ .../druid/query/topn/PooledTopNAlgorithm.java | 401 ++++++ .../io/druid/query/topn/TopNAlgorithm.java | 45 + .../query/topn/TopNAlgorithmSelector.java | 73 ++ .../io/druid/query/topn/TopNBinaryFn.java | 126 ++ .../topn/TopNLexicographicResultBuilder.java | 134 ++ .../java/io/druid/query/topn/TopNMapFn.java | 65 + .../io/druid/query/topn/TopNMetricSpec.java | 58 + .../query/topn/TopNMetricSpecBuilder.java | 35 + .../query/topn/TopNNumericResultBuilder.java | 153 +++ .../java/io/druid/query/topn/TopNParams.java | 61 + .../java/io/druid/query/topn/TopNQuery.java | 211 ++++ .../io/druid/query/topn/TopNQueryBuilder.java | 290 +++++ .../io/druid/query/topn/TopNQueryConfig.java | 39 + .../io/druid/query/topn/TopNQueryEngine.java | 117 ++ .../query/topn/TopNQueryQueryToolChest.java | 405 +++++++ .../query/topn/TopNQueryRunnerFactory.java | 108 ++ .../druid/query/topn/TopNResultBuilder.java | 48 + .../io/druid/query/topn/TopNResultMerger.java | 40 + .../io/druid/query/topn/TopNResultValue.java | 107 ++ .../java/io/druid/query/TestQueryRunners.java | 83 ++ .../io/druid/query/topn/TopNBinaryFnTest.java | 458 +++++++ .../druid/query/topn/TopNQueryRunnerTest.java | 1077 +++++++++++++++++ .../query/topn/TopNQueryRunnerTestHelper.java | 73 ++ .../druid/guice/QueryRunnerFactoryModule.java | 3 + .../io/druid/guice/QueryToolChestModule.java | 5 + 39 files changed, 5681 insertions(+), 1 deletion(-) create mode 100644 docs/content/TopNMetricSpec.md create mode 100644 docs/content/TopNQuery.md create mode 100644 processing/src/main/java/io/druid/query/topn/AggregateTopNMetricFirstAlgorithm.java create mode 100644 processing/src/main/java/io/druid/query/topn/BaseTopNAlgorithm.java create mode 100644 processing/src/main/java/io/druid/query/topn/BySegmentTopNResultValue.java create mode 100644 processing/src/main/java/io/druid/query/topn/DimExtractionTopNAlgorithm.java create mode 100644 processing/src/main/java/io/druid/query/topn/DimValHolder.java create mode 100644 processing/src/main/java/io/druid/query/topn/DimensionAndMetricValueExtractor.java create mode 100644 processing/src/main/java/io/druid/query/topn/InvertedTopNMetricSpec.java create mode 100644 processing/src/main/java/io/druid/query/topn/LegacyTopNMetricSpec.java create mode 100644 processing/src/main/java/io/druid/query/topn/LexicographicTopNMetricSpec.java create mode 100644 processing/src/main/java/io/druid/query/topn/NumericTopNMetricSpec.java create mode 100644 processing/src/main/java/io/druid/query/topn/PooledTopNAlgorithm.java create mode 100644 processing/src/main/java/io/druid/query/topn/TopNAlgorithm.java create mode 100644 processing/src/main/java/io/druid/query/topn/TopNAlgorithmSelector.java create mode 100644 processing/src/main/java/io/druid/query/topn/TopNBinaryFn.java create mode 100644 processing/src/main/java/io/druid/query/topn/TopNLexicographicResultBuilder.java create mode 100644 processing/src/main/java/io/druid/query/topn/TopNMapFn.java create mode 100644 processing/src/main/java/io/druid/query/topn/TopNMetricSpec.java create mode 100644 processing/src/main/java/io/druid/query/topn/TopNMetricSpecBuilder.java create mode 100644 processing/src/main/java/io/druid/query/topn/TopNNumericResultBuilder.java create mode 100644 processing/src/main/java/io/druid/query/topn/TopNParams.java create mode 100644 processing/src/main/java/io/druid/query/topn/TopNQuery.java create mode 100644 processing/src/main/java/io/druid/query/topn/TopNQueryBuilder.java create mode 100644 processing/src/main/java/io/druid/query/topn/TopNQueryConfig.java create mode 100644 processing/src/main/java/io/druid/query/topn/TopNQueryEngine.java create mode 100644 processing/src/main/java/io/druid/query/topn/TopNQueryQueryToolChest.java create mode 100644 processing/src/main/java/io/druid/query/topn/TopNQueryRunnerFactory.java create mode 100644 processing/src/main/java/io/druid/query/topn/TopNResultBuilder.java create mode 100644 processing/src/main/java/io/druid/query/topn/TopNResultMerger.java create mode 100644 processing/src/main/java/io/druid/query/topn/TopNResultValue.java create mode 100644 processing/src/test/java/io/druid/query/TestQueryRunners.java create mode 100644 processing/src/test/java/io/druid/query/topn/TopNBinaryFnTest.java create mode 100644 processing/src/test/java/io/druid/query/topn/TopNQueryRunnerTest.java create mode 100644 processing/src/test/java/io/druid/query/topn/TopNQueryRunnerTestHelper.java diff --git a/docs/content/TopNMetricSpec.md b/docs/content/TopNMetricSpec.md new file mode 100644 index 00000000000..672f5d352f6 --- /dev/null +++ b/docs/content/TopNMetricSpec.md @@ -0,0 +1,45 @@ +--- +layout: doc_page +--- +TopNMetricSpec +================== + +The topN metric spec specifies how topN values should be sorted. + +## Numeric TopNMetricSpec + +The simplest metric specification is a String value indicating the metric to sort topN results by. They are included in a topN query with: + +```json +"metric": +``` + +The metric field can also be given as a JSON object. The grammar for dimension values sorted by numeric value is shown below: + +```json +"metric": { + "type": "numeric", + "metric": "" +} +``` + +|property|description|required?| +|--------|-----------|---------| +|type|this indicates a numeric sort|yes| +|metric|the actual metric field in which results will be sorted by|yes| + +## Lexicographic TopNMetricSpec + +The grammar for dimension values sorted lexicographically is as follows: + +```json +"metric": { + "type": "lexicographic", + "previousStop": "" +} +``` + +|property|description|required?| +|--------|-----------|---------| +|type|this indicates a lexicographic sort|yes| +|previousStop|the starting point of the lexicographic sort. For example, if a previousStop value is 'b', all values before 'b' are discarded. This field can be used to paginate through all the dimension values.|no| diff --git a/docs/content/TopNQuery.md b/docs/content/TopNQuery.md new file mode 100644 index 00000000000..e0a0fc37077 --- /dev/null +++ b/docs/content/TopNQuery.md @@ -0,0 +1,119 @@ +--- +layout: doc_page +--- +TopN queries +================== + +TopN queries return a sorted set of results for the values in a given dimension according to some criteria. Conceptually, they can be thought of as an approximate [GroupByQuery](GroupByQuery.html) over a single dimension with an [Ordering](Ordering.html) spec. TopNs are much faster and resource efficient than GroupBys for this use case. These types of queries take a topN query object and return an array of JSON objects where each object represents a value asked for by the topN query. + +A topN query object looks like: + +```json + "queryType": "topN", + "dataSource": "sample_data", + "dimension": "sample_dim", + "threshold": 5, + "metric": "count", + "granularity": "all", + "filter": { + "type": "and", + "fields": [ + { + "type": "selector", + "dimension": "dim1", + "value": "some_value" + }, + { + "type": "selector", + "dimension": "dim2", + "value": "some_other_val" + } + ] + }, + "aggregations": [ + { + "type": "longSum", + "name": "count", + "fieldName": "count" + }, + { + "type": "doubleSum", + "name": "some_metric", + "fieldName": "some_metric" + } + ], + "postAggregations": [ + { + "type": "arithmetic", + "name": "sample_divide", + "fn": "/", + "fields": [ + { + "type": "fieldAccess", + "name": "some_metric", + "fieldName": "some_metric" + }, + { + "type": "fieldAccess", + "name": "count", + "fieldName": "count" + } + ] + } + ], + "intervals": [ + "2013-08-31T00:00:00.000/2013-09-03T00:00:00.000" + ] +} +``` + +There are 10 parts to a topN query, but 7 of them are shared with [TimeseriesQuery](TimeseriesQuery.html). Please review [TimeseriesQuery](TimeseriesQuery.html) for meanings of fields not defined below. + +|property|description|required?| +|--------|-----------|---------| +|dimension|A JSON object defining the dimension that you want the top taken for. For more info, see [DimensionSpecs](DimensionSpecs.html)|yes| +|threshold|An integer defining the N in the topN (i.e. how many you want in the top list)|yes| +|metric|A JSON object specifying the metric to sort by for the top list. For more info, see [TopNMetricSpec](TopNMetricSpec.html).|yes| + +Please note the context JSON object is also available for topN queries and should be used with the same caution as the timeseries case. +The format of the results would look like so: + +```json +[ + { + "timestamp": "2013-08-31T00:00:00.000Z", + "result": [ + { + "user": "67.173.175.77", + "count": 111, + "some_metrics": 10669, + "average": 96.11711711711712 + }, + { + "user": "24.10.49.170", + "count": 88, + "some_metrics": 28344, + "average": 322.09090909090907 + }, + { + "user": "72.193.24.148", + "count": 70, + "some_metrics": 871, + "average": 12.442857142857143 + }, + { + "user": "108.46.28.47", + "count": 62, + "some_metrics": 815, + "average": 13.14516129032258 + }, + { + "user": "99.181.143.133", + "count": 60, + "some_metrics": 2787, + "average": 46.45 + } + ] + } +] +``` diff --git a/docs/content/toc.textile b/docs/content/toc.textile index 802d94ef434..2a5f7ed185d 100644 --- a/docs/content/toc.textile +++ b/docs/content/toc.textile @@ -42,6 +42,8 @@ h2. Querying ** "SegmentMetadataQuery":./SegmentMetadataQuery.html ** "TimeBoundaryQuery":./TimeBoundaryQuery.html ** "TimeseriesQuery":./TimeseriesQuery.html +** "TopNQuery":./TopNQuery.html +*** "TopNMetricSpec":./TopNMetricSpec.html h2. Architecture * "Design":./Design.html diff --git a/processing/src/main/java/io/druid/query/Query.java b/processing/src/main/java/io/druid/query/Query.java index daf1410308e..f4ad958e75d 100644 --- a/processing/src/main/java/io/druid/query/Query.java +++ b/processing/src/main/java/io/druid/query/Query.java @@ -28,6 +28,7 @@ import io.druid.query.search.search.SearchQuery; import io.druid.query.spec.QuerySegmentSpec; import io.druid.query.timeboundary.TimeBoundaryQuery; import io.druid.query.timeseries.TimeseriesQuery; +import io.druid.query.topn.TopNQuery; import org.joda.time.Duration; import org.joda.time.Interval; @@ -40,7 +41,8 @@ import java.util.Map; @JsonSubTypes.Type(name = Query.SEARCH, value = SearchQuery.class), @JsonSubTypes.Type(name = Query.TIME_BOUNDARY, value = TimeBoundaryQuery.class), @JsonSubTypes.Type(name = Query.GROUP_BY, value = GroupByQuery.class), - @JsonSubTypes.Type(name = Query.SEGMENT_METADATA, value = SegmentMetadataQuery.class) + @JsonSubTypes.Type(name = Query.SEGMENT_METADATA, value = SegmentMetadataQuery.class), + @JsonSubTypes.Type(name = Query.TOPN, value = TopNQuery.class) }) public interface Query { @@ -49,6 +51,7 @@ public interface Query public static final String TIME_BOUNDARY = "timeBoundary"; public static final String GROUP_BY = "groupBy"; public static final String SEGMENT_METADATA = "segmentMetadata"; + public static final String TOPN = "topN"; public String getDataSource(); diff --git a/processing/src/main/java/io/druid/query/topn/AggregateTopNMetricFirstAlgorithm.java b/processing/src/main/java/io/druid/query/topn/AggregateTopNMetricFirstAlgorithm.java new file mode 100644 index 00000000000..ea878736bbd --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/AggregateTopNMetricFirstAlgorithm.java @@ -0,0 +1,174 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.google.common.collect.Lists; +import com.metamx.common.ISE; +import io.druid.collections.StupidPool; +import io.druid.query.aggregation.AggregatorFactory; +import io.druid.query.aggregation.PostAggregator; +import io.druid.segment.Capabilities; +import io.druid.segment.Cursor; +import io.druid.segment.DimensionSelector; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +/** + */ +public class AggregateTopNMetricFirstAlgorithm implements TopNAlgorithm +{ + private final Capabilities capabilities; + private final TopNQuery query; + private final Comparator comparator; + private final StupidPool bufferPool; + + public AggregateTopNMetricFirstAlgorithm( + Capabilities capabilities, + TopNQuery query, + StupidPool bufferPool + ) + { + this.capabilities = capabilities; + this.query = query; + this.comparator = query.getTopNMetricSpec() + .getComparator(query.getAggregatorSpecs(), query.getPostAggregatorSpecs()); + this.bufferPool = bufferPool; + } + + + @Override + public TopNParams makeInitParams( + DimensionSelector dimSelector, Cursor cursor + ) + { + return new TopNParams(dimSelector, cursor, dimSelector.getValueCardinality(), Integer.MAX_VALUE); + } + + @Override + public TopNResultBuilder makeResultBuilder(TopNParams params) + { + return query.getTopNMetricSpec().getResultBuilder( + params.getCursor().getTime(), query.getDimensionSpec(), query.getThreshold(), comparator + ); + } + + @Override + public void run( + TopNParams params, TopNResultBuilder resultBuilder, int[] ints + ) + { + final TopNResultBuilder singleMetricResultBuilder = makeResultBuilder(params); + final String metric = ((NumericTopNMetricSpec) query.getTopNMetricSpec()).getMetric(); + + // Find either the aggregator or post aggregator to do the topN over + List condensedAggs = Lists.newArrayList(); + for (AggregatorFactory aggregatorSpec : query.getAggregatorSpecs()) { + if (aggregatorSpec.getName().equalsIgnoreCase(metric)) { + condensedAggs.add(aggregatorSpec); + break; + } + } + List condensedPostAggs = Lists.newArrayList(); + if (condensedAggs.isEmpty()) { + for (PostAggregator postAggregator : query.getPostAggregatorSpecs()) { + if (postAggregator.getName().equalsIgnoreCase(metric)) { + condensedPostAggs.add(postAggregator); + + // Add all dependent metrics + for (AggregatorFactory aggregatorSpec : query.getAggregatorSpecs()) { + if (postAggregator.getDependentFields().contains(aggregatorSpec.getName())) { + condensedAggs.add(aggregatorSpec); + } + } + break; + } + } + } + if (condensedAggs.isEmpty() && condensedPostAggs.isEmpty()) { + throw new ISE("WTF! Can't find the metric to do topN over?"); + } + + // Run topN for only a single metric + TopNQuery singleMetricQuery = new TopNQueryBuilder().copy(query) + .aggregators(condensedAggs) + .postAggregators(condensedPostAggs) + .build(); + + PooledTopNAlgorithm singleMetricAlgo = new PooledTopNAlgorithm(capabilities, singleMetricQuery, bufferPool); + PooledTopNAlgorithm.PooledTopNParams singleMetricParam = null; + int[] dimValSelector = null; + try { + singleMetricParam = singleMetricAlgo.makeInitParams(params.getDimSelector(), params.getCursor()); + singleMetricAlgo.run( + singleMetricParam, + singleMetricResultBuilder, + null + ); + + // Get only the topN dimension values + dimValSelector = getDimValSelectorForTopNMetric(singleMetricParam, singleMetricResultBuilder); + } + finally { + if (singleMetricParam != null) { + singleMetricAlgo.cleanup(singleMetricParam); + } + } + + PooledTopNAlgorithm allMetricAlgo = new PooledTopNAlgorithm(capabilities, query, bufferPool); + PooledTopNAlgorithm.PooledTopNParams allMetricsParam = null; + try { + // Run topN for all metrics for top N dimension values + allMetricsParam = allMetricAlgo.makeInitParams(params.getDimSelector(), params.getCursor()); + allMetricAlgo.run( + allMetricsParam, + resultBuilder, + dimValSelector + ); + } + finally { + if (allMetricsParam != null) { + allMetricAlgo.cleanup(allMetricsParam); + } + } + } + + @Override + public void cleanup(TopNParams params) + { + } + + private int[] getDimValSelectorForTopNMetric(TopNParams params, TopNResultBuilder resultBuilder) + { + int[] dimValSelector = new int[params.getDimSelector().getValueCardinality()]; + Arrays.fill(dimValSelector, SKIP_POSITION_VALUE); + + Iterator dimValIter = resultBuilder.getTopNIterator(); + while (dimValIter.hasNext()) { + int dimValIndex = (Integer) dimValIter.next().getDimValIndex(); + dimValSelector[dimValIndex] = INIT_POSITION_VALUE; + } + + return dimValSelector; + } +} diff --git a/processing/src/main/java/io/druid/query/topn/BaseTopNAlgorithm.java b/processing/src/main/java/io/druid/query/topn/BaseTopNAlgorithm.java new file mode 100644 index 00000000000..47093cea8a5 --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/BaseTopNAlgorithm.java @@ -0,0 +1,234 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.metamx.common.Pair; +import io.druid.query.aggregation.Aggregator; +import io.druid.query.aggregation.AggregatorFactory; +import io.druid.query.aggregation.BufferAggregator; +import io.druid.segment.Capabilities; +import io.druid.segment.Cursor; +import io.druid.segment.DimensionSelector; + +import java.util.Arrays; +import java.util.List; + +/** + */ +public abstract class BaseTopNAlgorithm + implements TopNAlgorithm +{ + protected static Aggregator[] makeAggregators(Cursor cursor, List aggregatorSpecs) + { + Aggregator[] aggregators = new Aggregator[aggregatorSpecs.size()]; + int aggregatorIndex = 0; + for (AggregatorFactory spec : aggregatorSpecs) { + aggregators[aggregatorIndex] = spec.factorize(cursor); + ++aggregatorIndex; + } + return aggregators; + } + + protected static BufferAggregator[] makeBufferAggregators(Cursor cursor, List aggregatorSpecs) + { + BufferAggregator[] aggregators = new BufferAggregator[aggregatorSpecs.size()]; + int aggregatorIndex = 0; + for (AggregatorFactory spec : aggregatorSpecs) { + aggregators[aggregatorIndex] = spec.factorizeBuffered(cursor); + ++aggregatorIndex; + } + return aggregators; + } + + private final Capabilities capabilities; + + protected BaseTopNAlgorithm(Capabilities capabilities) + { + this.capabilities = capabilities; + } + + @Override + public void run( + Parameters params, + TopNResultBuilder resultBuilder, + DimValSelector dimValSelector + ) + { + boolean hasDimValSelector = (dimValSelector != null); + + final int cardinality = params.getCardinality(); + int numProcessed = 0; + while (numProcessed < cardinality) { + final int numToProcess = Math.min(params.getNumValuesPerPass(), cardinality - numProcessed); + + params.getCursor().reset(); + + DimValSelector theDimValSelector; + if (!hasDimValSelector) { + theDimValSelector = makeDimValSelector(params, numProcessed, numToProcess); + } else { + theDimValSelector = updateDimValSelector(dimValSelector, numProcessed, numToProcess); + } + + DimValAggregateStore aggregatesStore = makeDimValAggregateStore(params); + + scanAndAggregate(params, theDimValSelector, aggregatesStore, numProcessed); + + updateResults(params, theDimValSelector, aggregatesStore, resultBuilder); + + closeAggregators(aggregatesStore); + + numProcessed += numToProcess; + } + } + + protected abstract DimValSelector makeDimValSelector(Parameters params, int numProcessed, int numToProcess); + + protected abstract DimValSelector updateDimValSelector( + DimValSelector dimValSelector, + int numProcessed, + int numToProcess + ); + + protected abstract DimValAggregateStore makeDimValAggregateStore(Parameters params); + + protected abstract void scanAndAggregate( + Parameters params, + DimValSelector dimValSelector, + DimValAggregateStore dimValAggregateStore, + int numProcessed + ); + + protected abstract void updateResults( + Parameters params, + DimValSelector dimValSelector, + DimValAggregateStore dimValAggregateStore, + TopNResultBuilder resultBuilder + ); + + protected abstract void closeAggregators( + DimValAggregateStore dimValAggregateStore + ); + + protected class AggregatorArrayProvider extends BaseArrayProvider + { + Aggregator[][] expansionAggs; + int cardinality; + + public AggregatorArrayProvider(DimensionSelector dimSelector, TopNQuery query, int cardinality) + { + super(dimSelector, query, capabilities); + + this.expansionAggs = new Aggregator[cardinality][]; + this.cardinality = cardinality; + } + + @Override + public Aggregator[][] build() + { + Pair startEnd = computeStartEnd(cardinality); + + Arrays.fill(expansionAggs, 0, startEnd.lhs, EMPTY_ARRAY); + Arrays.fill(expansionAggs, startEnd.lhs, startEnd.rhs, null); + Arrays.fill(expansionAggs, startEnd.rhs, expansionAggs.length, EMPTY_ARRAY); + + return expansionAggs; + } + } + + protected static abstract class BaseArrayProvider implements TopNMetricSpecBuilder + { + private volatile String previousStop; + private volatile boolean ignoreAfterThreshold; + private volatile int ignoreFirstN; + private volatile int keepOnlyN; + + private final DimensionSelector dimSelector; + private final TopNQuery query; + private final Capabilities capabilities; + + public BaseArrayProvider( + DimensionSelector dimSelector, + TopNQuery query, + Capabilities capabilities + ) + { + this.dimSelector = dimSelector; + this.query = query; + this.capabilities = capabilities; + + previousStop = null; + ignoreAfterThreshold = false; + ignoreFirstN = 0; + keepOnlyN = dimSelector.getValueCardinality(); + } + + @Override + public void skipTo(String previousStop) + { + if (capabilities.dimensionValuesSorted()) { + this.previousStop = previousStop; + } + } + + @Override + public void ignoreAfterThreshold() + { + ignoreAfterThreshold = true; + } + + @Override + public void ignoreFirstN(int n) + { + ignoreFirstN = n; + } + + @Override + public void keepOnlyN(int n) + { + keepOnlyN = n; + } + + protected Pair computeStartEnd(int cardinality) + { + int startIndex = ignoreFirstN; + + if (previousStop != null) { + int lookupId = dimSelector.lookupId(previousStop) + 1; + if (lookupId < 0) { + lookupId *= -1; + } + if (lookupId > ignoreFirstN + keepOnlyN) { + startIndex = ignoreFirstN + keepOnlyN; + } else { + startIndex = Math.max(lookupId, startIndex); + } + } + + int endIndex = Math.min(ignoreFirstN + keepOnlyN, cardinality); + + if (ignoreAfterThreshold && query.getDimensionsFilter() == null) { + endIndex = Math.min(endIndex, startIndex + query.getThreshold()); + } + + return Pair.of(startIndex, endIndex); + } + } +} diff --git a/processing/src/main/java/io/druid/query/topn/BySegmentTopNResultValue.java b/processing/src/main/java/io/druid/query/topn/BySegmentTopNResultValue.java new file mode 100644 index 00000000000..56849d6fd47 --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/BySegmentTopNResultValue.java @@ -0,0 +1,90 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonValue; +import io.druid.query.BySegmentResultValue; +import io.druid.query.Result; + +import java.util.List; + +/** + */ +public class BySegmentTopNResultValue extends TopNResultValue implements BySegmentResultValue +{ + private final List> results; + private final String segmentId; + private final String intervalString; + + @JsonCreator + public BySegmentTopNResultValue( + @JsonProperty("results") List> results, + @JsonProperty("segment") String segmentId, + @JsonProperty("interval") String intervalString + ) + { + super(null); + + this.results = results; + this.segmentId = segmentId; + this.intervalString = intervalString; + } + + @Override + @JsonValue(false) + public List getValue() + { + throw new UnsupportedOperationException(); + } + + + @Override + @JsonProperty("results") + public List> getResults() + { + return results; + } + + @Override + @JsonProperty("segment") + public String getSegmentId() + { + return segmentId; + } + + @Override + @JsonProperty("interval") + public String getIntervalString() + { + return intervalString; + } + + @Override + public String toString() + { + return "BySegmentTopNResultValue{" + + "results=" + results + + ", segmentId='" + segmentId + '\'' + + ", intervalString='" + intervalString + '\'' + + '}'; + } +} diff --git a/processing/src/main/java/io/druid/query/topn/DimExtractionTopNAlgorithm.java b/processing/src/main/java/io/druid/query/topn/DimExtractionTopNAlgorithm.java new file mode 100644 index 00000000000..95ce5312e9d --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/DimExtractionTopNAlgorithm.java @@ -0,0 +1,169 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.google.common.collect.Maps; +import io.druid.query.aggregation.Aggregator; +import io.druid.segment.Capabilities; +import io.druid.segment.Cursor; +import io.druid.segment.DimensionSelector; +import io.druid.segment.data.IndexedInts; + +import java.util.Comparator; +import java.util.Map; + +/** + */ +public class DimExtractionTopNAlgorithm extends BaseTopNAlgorithm, TopNParams> +{ + private final TopNQuery query; + private final Comparator comparator; + + public DimExtractionTopNAlgorithm( + Capabilities capabilities, + TopNQuery query + ) + { + super(capabilities); + + this.query = query; + this.comparator = query.getTopNMetricSpec() + .getComparator(query.getAggregatorSpecs(), query.getPostAggregatorSpecs()); + } + + @Override + public TopNParams makeInitParams( + final DimensionSelector dimSelector, final Cursor cursor + ) + { + return new TopNParams(dimSelector, cursor, dimSelector.getValueCardinality(), Integer.MAX_VALUE); + } + + @Override + public TopNResultBuilder makeResultBuilder(TopNParams params) + { + return query.getTopNMetricSpec().getResultBuilder( + params.getCursor().getTime(), query.getDimensionSpec(), query.getThreshold(), comparator + ); + } + + @Override + protected Aggregator[][] makeDimValSelector(TopNParams params, int numProcessed, int numToProcess) + { + return query.getTopNMetricSpec().configureOptimizer( + new AggregatorArrayProvider(params.getDimSelector(), query, params.getCardinality()) + ).build(); + } + + @Override + protected Aggregator[][] updateDimValSelector(Aggregator[][] aggregators, int numProcessed, int numToProcess) + { + return aggregators; + } + + @Override + protected Map makeDimValAggregateStore(TopNParams params) + { + return Maps.newHashMap(); + } + + @Override + public void scanAndAggregate( + TopNParams params, + Aggregator[][] rowSelector, + Map aggregatesStore, + int numProcessed + ) + { + final Cursor cursor = params.getCursor(); + final DimensionSelector dimSelector = params.getDimSelector(); + + while (!cursor.isDone()) { + final IndexedInts dimValues = dimSelector.getRow(); + + for (int i = 0; i < dimValues.size(); ++i) { + final int dimIndex = dimValues.get(i); + + Aggregator[] theAggregators = rowSelector[dimIndex]; + if (theAggregators == null) { + String key = query.getDimensionSpec().getDimExtractionFn().apply(dimSelector.lookupName(dimIndex)); + if (key == null) { + rowSelector[dimIndex] = EMPTY_ARRAY; + continue; + } + theAggregators = aggregatesStore.get(key); + if (theAggregators == null) { + theAggregators = makeAggregators(cursor, query.getAggregatorSpecs()); + aggregatesStore.put(key, theAggregators); + } + rowSelector[dimIndex] = theAggregators; + } + + for (Aggregator aggregator : theAggregators) { + aggregator.aggregate(); + } + } + + cursor.advance(); + } + } + + @Override + protected void updateResults( + TopNParams params, + Aggregator[][] rowSelector, + Map aggregatesStore, + TopNResultBuilder resultBuilder + ) + { + for (Map.Entry entry : aggregatesStore.entrySet()) { + Aggregator[] aggs = entry.getValue(); + if (aggs != null && aggs.length > 0) { + Object[] vals = new Object[aggs.length]; + for (int i = 0; i < aggs.length; i++) { + vals[i] = aggs[i].get(); + } + + resultBuilder.addEntry( + entry.getKey(), + entry.getKey(), + vals, + query.getAggregatorSpecs(), + query.getPostAggregatorSpecs() + ); + } + } + } + + @Override + protected void closeAggregators(Map stringMap) + { + for (Aggregator[] aggregators : stringMap.values()) { + for (Aggregator agg : aggregators) { + agg.close(); + } + } + } + + @Override + public void cleanup(TopNParams params) + { + } +} diff --git a/processing/src/main/java/io/druid/query/topn/DimValHolder.java b/processing/src/main/java/io/druid/query/topn/DimValHolder.java new file mode 100644 index 00000000000..f17c77685c0 --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/DimValHolder.java @@ -0,0 +1,110 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import java.util.Map; + +/** + */ +public class DimValHolder +{ + private final Object topNMetricVal; + private final String dimName; + private final Object dimValIndex; + private final Map metricValues; + + public DimValHolder( + Object topNMetricVal, + String dimName, + Object dimValIndex, + Map metricValues + ) + { + this.topNMetricVal = topNMetricVal; + this.dimName = dimName; + this.dimValIndex = dimValIndex; + this.metricValues = metricValues; + } + + public Object getTopNMetricVal() + { + return topNMetricVal; + } + + public String getDimName() + { + return dimName; + } + + public Object getDimValIndex() + { + return dimValIndex; + } + + public Map getMetricValues() + { + return metricValues; + } + + public static class Builder + { + private Object topNMetricVal; + private String dirName; + private Object dimValIndex; + private Map metricValues; + + public Builder() + { + topNMetricVal = null; + dirName = null; + dimValIndex = null; + metricValues = null; + } + + public Builder withTopNMetricVal(Object topNMetricVal) + { + this.topNMetricVal = topNMetricVal; + return this; + } + + public Builder withDirName(String dirName) + { + this.dirName = dirName; + return this; + } + + public Builder withDimValIndex(Object dimValIndex) + { + this.dimValIndex = dimValIndex; + return this; + } + + public Builder withMetricValues(Map metricValues) + { + this.metricValues = metricValues; + return this; + } + + public DimValHolder build() + { + return new DimValHolder(topNMetricVal, dirName, dimValIndex, metricValues); + } + } +} diff --git a/processing/src/main/java/io/druid/query/topn/DimensionAndMetricValueExtractor.java b/processing/src/main/java/io/druid/query/topn/DimensionAndMetricValueExtractor.java new file mode 100644 index 00000000000..500074cebbd --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/DimensionAndMetricValueExtractor.java @@ -0,0 +1,83 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.fasterxml.jackson.annotation.JsonCreator; +import io.druid.query.MetricValueExtractor; + +import java.util.Map; + +/** + */ +public class DimensionAndMetricValueExtractor extends MetricValueExtractor +{ + private final Map value; + + @JsonCreator + public DimensionAndMetricValueExtractor(Map value) + { + super(value); + + this.value = value; + } + + public String getStringDimensionValue(String dimension) + { + return (String) value.get(dimension); + } + + public Object getDimensionValue(String dimension) + { + return value.get(dimension); + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + DimensionAndMetricValueExtractor that = (DimensionAndMetricValueExtractor) o; + + if (value != null ? !value.equals(that.value) : that.value != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() + { + return value != null ? value.hashCode() : 0; + } + + @Override + public String toString() + { + return "DimensionAndMetricValueExtractor{" + + "value=" + value + + '}'; + } +} diff --git a/processing/src/main/java/io/druid/query/topn/InvertedTopNMetricSpec.java b/processing/src/main/java/io/druid/query/topn/InvertedTopNMetricSpec.java new file mode 100644 index 00000000000..2e458295387 --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/InvertedTopNMetricSpec.java @@ -0,0 +1,104 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.metamx.common.guava.Comparators; +import io.druid.query.aggregation.AggregatorFactory; +import io.druid.query.aggregation.PostAggregator; +import io.druid.query.dimension.DimensionSpec; +import org.joda.time.DateTime; + +import java.nio.ByteBuffer; +import java.util.Comparator; +import java.util.List; + +/** + */ +public class InvertedTopNMetricSpec implements TopNMetricSpec +{ + private static final byte CACHE_TYPE_ID = 0x3; + + private final TopNMetricSpec delegate; + + @JsonCreator + public InvertedTopNMetricSpec( + @JsonProperty("metric") TopNMetricSpec delegate + ) + { + this.delegate = delegate; + } + + @Override + public void verifyPreconditions( + List aggregatorSpecs, + List postAggregatorSpecs + ) + { + delegate.verifyPreconditions(aggregatorSpecs, postAggregatorSpecs); + } + + @JsonProperty("metric") + public TopNMetricSpec getDelegate() + { + return delegate; + } + + @Override + public Comparator getComparator( + List aggregatorSpecs, + List postAggregatorSpecs + ) + { + return Comparators.inverse(delegate.getComparator(aggregatorSpecs, postAggregatorSpecs)); + } + + @Override + public TopNResultBuilder getResultBuilder( + DateTime timestamp, + DimensionSpec dimSpec, + int threshold, + Comparator comparator + ) + { + return delegate.getResultBuilder(timestamp, dimSpec, threshold, comparator); + } + + @Override + public byte[] getCacheKey() + { + final byte[] cacheKey = delegate.getCacheKey(); + + return ByteBuffer.allocate(1 + cacheKey.length).put(CACHE_TYPE_ID).put(cacheKey).array(); + } + + @Override + public TopNMetricSpecBuilder configureOptimizer(TopNMetricSpecBuilder builder) + { + return delegate.configureOptimizer(builder); + } + + @Override + public void initTopNAlgorithmSelector(TopNAlgorithmSelector selector) + { + delegate.initTopNAlgorithmSelector(selector); + } +} diff --git a/processing/src/main/java/io/druid/query/topn/LegacyTopNMetricSpec.java b/processing/src/main/java/io/druid/query/topn/LegacyTopNMetricSpec.java new file mode 100644 index 00000000000..f185d799ea1 --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/LegacyTopNMetricSpec.java @@ -0,0 +1,51 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.metamx.common.IAE; + +import java.util.Map; + +/** + */ +public class LegacyTopNMetricSpec extends NumericTopNMetricSpec +{ + private static final String convertValue(Object metric) + { + final String retVal; + + if (metric instanceof String) { + retVal = (String) metric; + } else if (metric instanceof Map) { + retVal = (String) ((Map) metric).get("metric"); + } else { + throw new IAE("Unknown type[%s] for metric[%s]", metric.getClass(), metric); + } + + return retVal; + } + + @JsonCreator + public LegacyTopNMetricSpec(Object metric) + { + super(convertValue(metric)); + } +} diff --git a/processing/src/main/java/io/druid/query/topn/LexicographicTopNMetricSpec.java b/processing/src/main/java/io/druid/query/topn/LexicographicTopNMetricSpec.java new file mode 100644 index 00000000000..dca6d3ed651 --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/LexicographicTopNMetricSpec.java @@ -0,0 +1,121 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Charsets; +import com.google.common.primitives.UnsignedBytes; +import io.druid.query.aggregation.AggregatorFactory; +import io.druid.query.aggregation.PostAggregator; +import io.druid.query.dimension.DimensionSpec; +import org.joda.time.DateTime; + +import java.nio.ByteBuffer; +import java.util.Comparator; +import java.util.List; + +/** + */ +public class LexicographicTopNMetricSpec implements TopNMetricSpec +{ + private static final byte CACHE_TYPE_ID = 0x1; + + private static Comparator comparator = new Comparator() + { + @Override + public int compare(String s, String s2) + { + return UnsignedBytes.lexicographicalComparator().compare(s.getBytes(Charsets.UTF_8), s2.getBytes(Charsets.UTF_8)); + } + }; + + private final String previousStop; + + @JsonCreator + public LexicographicTopNMetricSpec( + @JsonProperty("previousStop") String previousStop + ) + { + this.previousStop = (previousStop == null) ? "" : previousStop; + } + + @Override + public void verifyPreconditions(List aggregatorSpecs, List postAggregatorSpecs) + { + } + + @JsonProperty + public String getPreviousStop() + { + return previousStop; + } + + + @Override + public Comparator getComparator(List aggregatorSpecs, List postAggregatorSpecs) + { + return comparator; + } + + @Override + public TopNResultBuilder getResultBuilder( + DateTime timestamp, + DimensionSpec dimSpec, + int threshold, + Comparator comparator + ) + { + return new TopNLexicographicResultBuilder(timestamp, dimSpec, threshold, previousStop, comparator); + } + + @Override + public byte[] getCacheKey() + { + byte[] previousStopBytes = previousStop.getBytes(Charsets.UTF_8); + + return ByteBuffer.allocate(1 + previousStopBytes.length) + .put(CACHE_TYPE_ID) + .put(previousStopBytes) + .array(); + } + + @Override + public TopNMetricSpecBuilder configureOptimizer(TopNMetricSpecBuilder builder) + { + builder.skipTo(previousStop); + builder.ignoreAfterThreshold(); + return builder; + } + + @Override + public void initTopNAlgorithmSelector(TopNAlgorithmSelector selector) + { + selector.setAggregateAllMetrics(true); + } + + @Override + public String toString() + { + return "LexicographicTopNMetricSpec{" + + "previousStop='" + previousStop + '\'' + + '}'; + } +} diff --git a/processing/src/main/java/io/druid/query/topn/NumericTopNMetricSpec.java b/processing/src/main/java/io/druid/query/topn/NumericTopNMetricSpec.java new file mode 100644 index 00000000000..4f0b6ebdc22 --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/NumericTopNMetricSpec.java @@ -0,0 +1,160 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Charsets; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import io.druid.query.aggregation.AggregatorFactory; +import io.druid.query.aggregation.PostAggregator; +import io.druid.query.dimension.DimensionSpec; +import org.joda.time.DateTime; + +import java.nio.ByteBuffer; +import java.util.Comparator; +import java.util.List; + +/** + */ +public class NumericTopNMetricSpec implements TopNMetricSpec +{ + private static final byte CACHE_TYPE_ID = 0x0; + + private final String metric; + + @JsonCreator + public NumericTopNMetricSpec( + @JsonProperty("metric") String metric + ) + { + this.metric = metric; + } + + @Override + public void verifyPreconditions(List aggregatorSpecs, List postAggregatorSpecs) + { + Preconditions.checkNotNull(metric, "metric can't be null"); + Preconditions.checkNotNull(aggregatorSpecs, "aggregations cannot be null"); + Preconditions.checkArgument(aggregatorSpecs.size() > 0, "Must have at least one AggregatorFactory"); + + final AggregatorFactory aggregator = Iterables.tryFind( + aggregatorSpecs, + new Predicate() + { + @Override + public boolean apply(AggregatorFactory input) + { + return input.getName().equals(metric); + } + } + ).orNull(); + + final PostAggregator postAggregator = Iterables.tryFind( + postAggregatorSpecs, + new Predicate() + { + @Override + public boolean apply(PostAggregator input) + { + return input.getName().equals(metric); + } + } + ).orNull(); + + Preconditions.checkArgument( + aggregator != null || postAggregator != null, + "Must have an AggregatorFactory or PostAggregator for metric[%s], gave[%s] and [%s]", + metric, + aggregatorSpecs, + postAggregatorSpecs + ); + } + + @JsonProperty + public String getMetric() + { + return metric; + } + + @Override + public Comparator getComparator(List aggregatorSpecs, List postAggregatorSpecs) + { + Comparator comp = null; + for (AggregatorFactory factory : aggregatorSpecs) { + if (metric.equals(factory.getName())) { + comp = factory.getComparator(); + break; + } + } + for (PostAggregator pf : postAggregatorSpecs) { + if (metric.equals(pf.getName())) { + comp = pf.getComparator(); + break; + } + } + + return comp; + } + + @Override + public TopNResultBuilder getResultBuilder( + DateTime timestamp, + DimensionSpec dimSpec, + int threshold, + Comparator comparator + ) + { + return new TopNNumericResultBuilder(timestamp, dimSpec, metric, threshold, comparator); + } + + @Override + public byte[] getCacheKey() + { + byte[] metricBytes = metric.getBytes(Charsets.UTF_8); + + return ByteBuffer.allocate(1 + metricBytes.length) + .put(CACHE_TYPE_ID) + .put(metricBytes) + .array(); + } + + @Override + public TopNMetricSpecBuilder configureOptimizer(TopNMetricSpecBuilder builder) + { + return builder; + } + + @Override + public void initTopNAlgorithmSelector(TopNAlgorithmSelector selector) + { + selector.setAggregateTopNMetricFirst(true); + } + + @Override + public String toString() + { + return "NumericTopNMetricSpec{" + + "metric='" + metric + '\'' + + '}'; + } +} diff --git a/processing/src/main/java/io/druid/query/topn/PooledTopNAlgorithm.java b/processing/src/main/java/io/druid/query/topn/PooledTopNAlgorithm.java new file mode 100644 index 00000000000..d87631c7b57 --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/PooledTopNAlgorithm.java @@ -0,0 +1,401 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.google.common.io.Closeables; +import com.metamx.common.Pair; +import io.druid.collections.ResourceHolder; +import io.druid.collections.StupidPool; +import io.druid.query.aggregation.BufferAggregator; +import io.druid.segment.Capabilities; +import io.druid.segment.Cursor; +import io.druid.segment.DimensionSelector; +import io.druid.segment.data.IndexedInts; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Comparator; + +/** + */ +public class PooledTopNAlgorithm extends BaseTopNAlgorithm +{ + private final Capabilities capabilities; + private final TopNQuery query; + private final Comparator comparator; + private final StupidPool bufferPool; + + public PooledTopNAlgorithm( + Capabilities capabilities, + TopNQuery query, + StupidPool bufferPool + ) + { + super(capabilities); + + this.capabilities = capabilities; + this.query = query; + this.comparator = query.getTopNMetricSpec() + .getComparator(query.getAggregatorSpecs(), query.getPostAggregatorSpecs()); + this.bufferPool = bufferPool; + } + + @Override + public PooledTopNParams makeInitParams( + DimensionSelector dimSelector, Cursor cursor + ) + { + ResourceHolder resultsBufHolder = bufferPool.take(); + ByteBuffer resultsBuf = resultsBufHolder.get(); + resultsBuf.clear(); + + final int cardinality = dimSelector.getValueCardinality(); + + final TopNMetricSpecBuilder arrayProvider = new BaseArrayProvider( + dimSelector, + query, + capabilities + ) + { + private final int[] positions = new int[cardinality]; + + @Override + public int[] build() + { + Pair startEnd = computeStartEnd(cardinality); + + Arrays.fill(positions, 0, startEnd.lhs, SKIP_POSITION_VALUE); + Arrays.fill(positions, startEnd.lhs, startEnd.rhs, INIT_POSITION_VALUE); + Arrays.fill(positions, startEnd.rhs, positions.length, SKIP_POSITION_VALUE); + + return positions; + } + }; + + final int numBytesToWorkWith = resultsBuf.remaining(); + final int[] aggregatorSizes = new int[query.getAggregatorSpecs().size()]; + int numBytesPerRecord = 0; + + for (int i = 0; i < query.getAggregatorSpecs().size(); ++i) { + aggregatorSizes[i] = query.getAggregatorSpecs().get(i).getMaxIntermediateSize(); + numBytesPerRecord += aggregatorSizes[i]; + } + + final int numValuesPerPass = numBytesToWorkWith / numBytesPerRecord; + + return PooledTopNParams.builder() + .withDimSelector(dimSelector) + .withCursor(cursor) + .withCardinality(cardinality) + .withResultsBufHolder(resultsBufHolder) + .withResultsBuf(resultsBuf) + .withArrayProvider(arrayProvider) + .withNumBytesPerRecord(numBytesPerRecord) + .withNumValuesPerPass(numValuesPerPass) + .withAggregatorSizes(aggregatorSizes) + .build(); + } + + @Override + public TopNResultBuilder makeResultBuilder(PooledTopNParams params) + { + return query.getTopNMetricSpec().getResultBuilder( + params.getCursor().getTime(), query.getDimensionSpec(), query.getThreshold(), comparator + ); + } + + @Override + protected int[] makeDimValSelector(PooledTopNParams params, int numProcessed, int numToProcess) + { + final TopNMetricSpecBuilder arrayProvider = params.getArrayProvider(); + + arrayProvider.ignoreFirstN(numProcessed); + arrayProvider.keepOnlyN(numToProcess); + return query.getTopNMetricSpec().configureOptimizer(arrayProvider).build(); + } + + protected int[] updateDimValSelector(int[] dimValSelector, int numProcessed, int numToProcess) + { + final int[] retVal = Arrays.copyOf(dimValSelector, dimValSelector.length); + + final int validEnd = Math.min(retVal.length, numProcessed + numToProcess); + final int end = Math.max(retVal.length, validEnd); + + Arrays.fill(retVal, 0, numProcessed, SKIP_POSITION_VALUE); + Arrays.fill(retVal, validEnd, end, SKIP_POSITION_VALUE); + + return retVal; + } + + @Override + protected BufferAggregator[] makeDimValAggregateStore(PooledTopNParams params) + { + return makeBufferAggregators(params.getCursor(), query.getAggregatorSpecs()); + } + + @Override + protected void scanAndAggregate( + PooledTopNParams params, + int[] positions, + BufferAggregator[] theAggregators, + int numProcessed + ) + { + final ByteBuffer resultsBuf = params.getResultsBuf(); + final int numBytesPerRecord = params.getNumBytesPerRecord(); + final int[] aggregatorSizes = params.getAggregatorSizes(); + final Cursor cursor = params.getCursor(); + final DimensionSelector dimSelector = params.getDimSelector(); + + while (!cursor.isDone()) { + final IndexedInts dimValues = dimSelector.getRow(); + + for (int i = 0; i < dimValues.size(); ++i) { + final int dimIndex = dimValues.get(i); + int position = positions[dimIndex]; + switch (position) { + case SKIP_POSITION_VALUE: + break; + case INIT_POSITION_VALUE: + positions[dimIndex] = (dimIndex - numProcessed) * numBytesPerRecord; + position = positions[dimIndex]; + for (int j = 0; j < theAggregators.length; ++j) { + theAggregators[j].init(resultsBuf, position); + position += aggregatorSizes[j]; + } + position = positions[dimIndex]; + default: + for (int j = 0; j < theAggregators.length; ++j) { + theAggregators[j].aggregate(resultsBuf, position); + position += aggregatorSizes[j]; + } + } + } + + cursor.advance(); + } + } + + @Override + protected void updateResults( + PooledTopNParams params, + int[] positions, + BufferAggregator[] theAggregators, + TopNResultBuilder resultBuilder + ) + { + final ByteBuffer resultsBuf = params.getResultsBuf(); + final int[] aggregatorSizes = params.getAggregatorSizes(); + final DimensionSelector dimSelector = params.getDimSelector(); + + for (int i = 0; i < positions.length; i++) { + int position = positions[i]; + if (position >= 0) { + Object[] vals = new Object[theAggregators.length]; + for (int j = 0; j < theAggregators.length; j++) { + vals[j] = theAggregators[j].get(resultsBuf, position); + position += aggregatorSizes[j]; + } + + resultBuilder.addEntry( + dimSelector.lookupName(i), + i, + vals, + query.getAggregatorSpecs(), + query.getPostAggregatorSpecs() + ); + } + } + } + + @Override + protected void closeAggregators(BufferAggregator[] bufferAggregators) + { + for(BufferAggregator agg : bufferAggregators) { + agg.close(); + } + } + + @Override + public void cleanup(PooledTopNParams params) + { + ResourceHolder resultsBufHolder = params.getResultsBufHolder(); + + if (resultsBufHolder != null) { + resultsBufHolder.get().clear(); + } + Closeables.closeQuietly(resultsBufHolder); + } + + public static class PooledTopNParams extends TopNParams + { + public static Builder builder() + { + return new Builder(); + } + + private final ResourceHolder resultsBufHolder; + private final ByteBuffer resultsBuf; + private final int[] aggregatorSizes; + private final int numBytesPerRecord; + private final TopNMetricSpecBuilder arrayProvider; + + public PooledTopNParams( + DimensionSelector dimSelector, + Cursor cursor, + int cardinality, + ResourceHolder resultsBufHolder, + ByteBuffer resultsBuf, + int[] aggregatorSizes, + int numBytesPerRecord, + int numValuesPerPass, + TopNMetricSpecBuilder arrayProvider + ) + { + super(dimSelector, cursor, cardinality, numValuesPerPass); + + this.resultsBufHolder = resultsBufHolder; + this.resultsBuf = resultsBuf; + this.aggregatorSizes = aggregatorSizes; + this.numBytesPerRecord = numBytesPerRecord; + this.arrayProvider = arrayProvider; + } + + public ResourceHolder getResultsBufHolder() + { + return resultsBufHolder; + } + + public ByteBuffer getResultsBuf() + { + return resultsBuf; + } + + public int[] getAggregatorSizes() + { + return aggregatorSizes; + } + + public int getNumBytesPerRecord() + { + return numBytesPerRecord; + } + + public TopNMetricSpecBuilder getArrayProvider() + { + return arrayProvider; + } + + public static class Builder + { + private DimensionSelector dimSelector; + private Cursor cursor; + private int cardinality; + private ResourceHolder resultsBufHolder; + private ByteBuffer resultsBuf; + private int[] aggregatorSizes; + private int numBytesPerRecord; + private int numValuesPerPass; + private TopNMetricSpecBuilder arrayProvider; + + public Builder() + { + dimSelector = null; + cursor = null; + cardinality = 0; + resultsBufHolder = null; + resultsBuf = null; + aggregatorSizes = null; + numBytesPerRecord = 0; + numValuesPerPass = 0; + arrayProvider = null; + } + + public Builder withDimSelector(DimensionSelector dimSelector) + { + this.dimSelector = dimSelector; + return this; + } + + public Builder withCursor(Cursor cursor) + { + this.cursor = cursor; + return this; + } + + public Builder withCardinality(int cardinality) + { + this.cardinality = cardinality; + return this; + } + + public Builder withResultsBufHolder(ResourceHolder resultsBufHolder) + { + this.resultsBufHolder = resultsBufHolder; + return this; + } + + public Builder withResultsBuf(ByteBuffer resultsBuf) + { + this.resultsBuf = resultsBuf; + return this; + } + + public Builder withAggregatorSizes(int[] aggregatorSizes) + { + this.aggregatorSizes = aggregatorSizes; + return this; + } + + public Builder withNumBytesPerRecord(int numBytesPerRecord) + { + this.numBytesPerRecord = numBytesPerRecord; + return this; + } + + public Builder withNumValuesPerPass(int numValuesPerPass) + { + this.numValuesPerPass = numValuesPerPass; + return this; + } + + public Builder withArrayProvider(TopNMetricSpecBuilder arrayProvider) + { + this.arrayProvider = arrayProvider; + return this; + } + + public PooledTopNParams build() + { + return new PooledTopNParams( + dimSelector, + cursor, + cardinality, + resultsBufHolder, + resultsBuf, + aggregatorSizes, + numBytesPerRecord, + numValuesPerPass, + arrayProvider + ); + } + } + } +} diff --git a/processing/src/main/java/io/druid/query/topn/TopNAlgorithm.java b/processing/src/main/java/io/druid/query/topn/TopNAlgorithm.java new file mode 100644 index 00000000000..89bac871441 --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/TopNAlgorithm.java @@ -0,0 +1,45 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import io.druid.query.aggregation.Aggregator; +import io.druid.segment.Cursor; +import io.druid.segment.DimensionSelector; + +/** + */ +public interface TopNAlgorithm +{ + public static final Aggregator[] EMPTY_ARRAY = {}; + public static final int INIT_POSITION_VALUE = -1; + public static final int SKIP_POSITION_VALUE = -2; + + public TopNParams makeInitParams(DimensionSelector dimSelector, Cursor cursor); + + public TopNResultBuilder makeResultBuilder(Parameters params); + + public void run( + Parameters params, + TopNResultBuilder resultBuilder, + DimValSelector dimValSelector + ); + + public void cleanup(Parameters params); +} diff --git a/processing/src/main/java/io/druid/query/topn/TopNAlgorithmSelector.java b/processing/src/main/java/io/druid/query/topn/TopNAlgorithmSelector.java new file mode 100644 index 00000000000..424a3aa5950 --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/TopNAlgorithmSelector.java @@ -0,0 +1,73 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +/** + */ +public class TopNAlgorithmSelector +{ + private final int cardinality; + private final int numBytesPerRecord; + + private volatile boolean hasDimExtractionFn; + private volatile boolean aggregateAllMetrics; + private volatile boolean aggregateTopNMetricFirst; + + public TopNAlgorithmSelector(int cardinality, int numBytesPerRecord) + { + this.cardinality = cardinality; + this.numBytesPerRecord = numBytesPerRecord; + } + + public void setHasDimExtractionFn(boolean hasDimExtractionFn) + { + this.hasDimExtractionFn = hasDimExtractionFn; + } + + public void setAggregateAllMetrics(boolean aggregateAllMetrics) + { + this.aggregateAllMetrics = aggregateAllMetrics; + } + + public void setAggregateTopNMetricFirst(boolean aggregateTopNMetricFirst) + { + // These are just heuristics based on an analysis of where an inflection point may lie to switch + // between different algorithms + // More info: https://metamarkets.atlassian.net/wiki/display/APP/Top+n+speeds+with+uniques + if (cardinality > 400000 && numBytesPerRecord > 100) { + this.aggregateTopNMetricFirst = aggregateTopNMetricFirst; + } + } + + public boolean isHasDimExtractionFn() + { + return hasDimExtractionFn; + } + + public boolean isAggregateAllMetrics() + { + return aggregateAllMetrics; + } + + public boolean isAggregateTopNMetricFirst() + { + return aggregateTopNMetricFirst; + } +} diff --git a/processing/src/main/java/io/druid/query/topn/TopNBinaryFn.java b/processing/src/main/java/io/druid/query/topn/TopNBinaryFn.java new file mode 100644 index 00000000000..437c28f640f --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/TopNBinaryFn.java @@ -0,0 +1,126 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.metamx.common.guava.nary.BinaryFn; +import io.druid.granularity.AllGranularity; +import io.druid.granularity.QueryGranularity; +import io.druid.query.Result; +import io.druid.query.aggregation.AggregatorFactory; +import io.druid.query.aggregation.PostAggregator; +import io.druid.query.dimension.DimensionSpec; +import org.joda.time.DateTime; + +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + */ +public class TopNBinaryFn implements BinaryFn, Result, Result> +{ + private final TopNResultMerger merger; + private final DimensionSpec dimSpec; + private final QueryGranularity gran; + private final String dimension; + private final TopNMetricSpec topNMetricSpec; + private final int threshold; + private final List aggregations; + private final List postAggregations; + private final Comparator comparator; + + public TopNBinaryFn( + final TopNResultMerger merger, + final QueryGranularity granularity, + final DimensionSpec dimSpec, + final TopNMetricSpec topNMetricSpec, + final int threshold, + final List aggregatorSpecs, + final List postAggregatorSpecs + ) + { + this.merger = merger; + this.dimSpec = dimSpec; + this.gran = granularity; + this.topNMetricSpec = topNMetricSpec; + this.threshold = threshold; + this.aggregations = aggregatorSpecs; + this.postAggregations = postAggregatorSpecs; + + this.dimension = dimSpec.getOutputName(); + this.comparator = topNMetricSpec.getComparator(aggregatorSpecs, postAggregatorSpecs); + } + + @Override + public Result apply(Result arg1, Result arg2) + { + if (arg1 == null) { + return merger.getResult(arg2, comparator); + } + if (arg2 == null) { + return merger.getResult(arg1, comparator); + } + + Map retVals = new LinkedHashMap(); + + TopNResultValue arg1Vals = arg1.getValue(); + TopNResultValue arg2Vals = arg2.getValue(); + + for (DimensionAndMetricValueExtractor arg1Val : arg1Vals) { + retVals.put(arg1Val.getStringDimensionValue(dimension), arg1Val); + } + for (DimensionAndMetricValueExtractor arg2Val : arg2Vals) { + final String dimensionValue = arg2Val.getStringDimensionValue(dimension); + DimensionAndMetricValueExtractor arg1Val = retVals.get(dimensionValue); + + if (arg1Val != null) { + Map retVal = new LinkedHashMap(); + + retVal.put(dimension, dimensionValue); + for (AggregatorFactory factory : aggregations) { + final String metricName = factory.getName(); + retVal.put(metricName, factory.combine(arg1Val.getMetric(metricName), arg2Val.getMetric(metricName))); + } + + for (PostAggregator pf : postAggregations) { + retVal.put(pf.getName(), pf.compute(retVal)); + } + + retVals.put(dimensionValue, new DimensionAndMetricValueExtractor(retVal)); + } else { + retVals.put(dimensionValue, arg2Val); + } + } + + final DateTime timestamp; + if (gran instanceof AllGranularity) { + timestamp = arg1.getTimestamp(); + } else { + timestamp = gran.toDateTime(gran.truncate(arg1.getTimestamp().getMillis())); + } + + TopNResultBuilder bob = topNMetricSpec.getResultBuilder(timestamp, dimSpec, threshold, comparator); + for (DimensionAndMetricValueExtractor extractor : retVals.values()) { + bob.addEntry(extractor); + } + return bob.build(); + } +} diff --git a/processing/src/main/java/io/druid/query/topn/TopNLexicographicResultBuilder.java b/processing/src/main/java/io/druid/query/topn/TopNLexicographicResultBuilder.java new file mode 100644 index 00000000000..37360dfb1cd --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/TopNLexicographicResultBuilder.java @@ -0,0 +1,134 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.google.common.collect.Maps; +import com.google.common.collect.MinMaxPriorityQueue; +import io.druid.query.Result; +import io.druid.query.aggregation.AggregatorFactory; +import io.druid.query.aggregation.PostAggregator; +import io.druid.query.dimension.DimensionSpec; +import org.joda.time.DateTime; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + */ +public class TopNLexicographicResultBuilder implements TopNResultBuilder +{ + private final DateTime timestamp; + private final DimensionSpec dimSpec; + private final String previousStop; + + private MinMaxPriorityQueue pQueue = null; + + public TopNLexicographicResultBuilder( + DateTime timestamp, + DimensionSpec dimSpec, + int threshold, + String previousStop, + final Comparator comparator + ) + { + this.timestamp = timestamp; + this.dimSpec = dimSpec; + this.previousStop = previousStop; + + instantiatePQueue(threshold, comparator); + } + + @Override + public TopNResultBuilder addEntry( + String dimName, + Object dimValIndex, + Object[] metricVals, + List aggFactories, + List postAggs + ) + { + Map metricValues = Maps.newLinkedHashMap(); + + if (dimName.compareTo(previousStop) > 0) { + metricValues.put(dimSpec.getOutputName(), dimName); + Iterator aggsIter = aggFactories.iterator(); + for (Object metricVal : metricVals) { + metricValues.put(aggsIter.next().getName(), metricVal); + } + for (PostAggregator postAgg : postAggs) { + metricValues.put(postAgg.getName(), postAgg.compute(metricValues)); + } + + pQueue.add(new DimValHolder.Builder().withDirName(dimName).withMetricValues(metricValues).build()); + } + + return this; + } + + @Override + public TopNResultBuilder addEntry(DimensionAndMetricValueExtractor dimensionAndMetricValueExtractor) + { + pQueue.add( + new DimValHolder.Builder().withDirName(dimensionAndMetricValueExtractor.getStringDimensionValue(dimSpec.getOutputName())) + .withMetricValues(dimensionAndMetricValueExtractor.getBaseObject()) + .build() + ); + + return this; + } + + @Override + public Iterator getTopNIterator() + { + return pQueue.iterator(); + } + + @Override + public Result build() + { + // Pull out top aggregated values + List> values = new ArrayList>(pQueue.size()); + while (!pQueue.isEmpty()) { + values.add(pQueue.remove().getMetricValues()); + } + + return new Result(timestamp, new TopNResultValue(values)); + } + + private void instantiatePQueue(int threshold, final Comparator comparator) + { + this.pQueue = MinMaxPriorityQueue.orderedBy( + new Comparator() + { + @Override + public int compare( + DimValHolder o1, + DimValHolder o2 + ) + { + return comparator.compare(o1.getDimName(), o2.getDimName()); + } + } + ).maximumSize(threshold).create(); + } +} diff --git a/processing/src/main/java/io/druid/query/topn/TopNMapFn.java b/processing/src/main/java/io/druid/query/topn/TopNMapFn.java new file mode 100644 index 00000000000..c013d546f7f --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/TopNMapFn.java @@ -0,0 +1,65 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.google.common.base.Function; +import io.druid.query.Result; +import io.druid.segment.Cursor; +import io.druid.segment.DimensionSelector; + +public class TopNMapFn implements Function> +{ + private final TopNQuery query; + private final TopNAlgorithm topNAlgorithm; + + + public TopNMapFn( + TopNQuery query, + TopNAlgorithm topNAlgorithm + ) + { + this.query = query; + this.topNAlgorithm = topNAlgorithm; + } + + @Override + @SuppressWarnings("unchecked") + public Result apply(Cursor cursor) + { + final DimensionSelector dimSelector = cursor.makeDimensionSelector(query.getDimensionSpec().getDimension()); + if (dimSelector == null) { + return null; + } + + TopNParams params = null; + try { + params = topNAlgorithm.makeInitParams(dimSelector, cursor); + + TopNResultBuilder resultBuilder = topNAlgorithm.makeResultBuilder(params); + + topNAlgorithm.run(params, resultBuilder, null); + + return resultBuilder.build(); + } + finally { + topNAlgorithm.cleanup(params); + } + } +} diff --git a/processing/src/main/java/io/druid/query/topn/TopNMetricSpec.java b/processing/src/main/java/io/druid/query/topn/TopNMetricSpec.java new file mode 100644 index 00000000000..c2baf13e3eb --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/TopNMetricSpec.java @@ -0,0 +1,58 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import io.druid.query.aggregation.AggregatorFactory; +import io.druid.query.aggregation.PostAggregator; +import io.druid.query.dimension.DimensionSpec; +import org.joda.time.DateTime; + +import java.util.Comparator; +import java.util.List; + +/** + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = LegacyTopNMetricSpec.class) +@JsonSubTypes(value = { + @JsonSubTypes.Type(name = "numeric", value = NumericTopNMetricSpec.class), + @JsonSubTypes.Type(name = "lexicographic", value = LexicographicTopNMetricSpec.class), + @JsonSubTypes.Type(name = "inverted", value = InvertedTopNMetricSpec.class) +}) +public interface TopNMetricSpec +{ + public void verifyPreconditions(List aggregatorSpecs, List postAggregatorSpecs); + + public Comparator getComparator(List aggregatorSpecs, List postAggregatorSpecs); + + public TopNResultBuilder getResultBuilder( + DateTime timestamp, + DimensionSpec dimSpec, + int threshold, + Comparator comparator + ); + + public byte[] getCacheKey(); + + public TopNMetricSpecBuilder configureOptimizer(TopNMetricSpecBuilder builder); + + public void initTopNAlgorithmSelector(TopNAlgorithmSelector selector); +} diff --git a/processing/src/main/java/io/druid/query/topn/TopNMetricSpecBuilder.java b/processing/src/main/java/io/druid/query/topn/TopNMetricSpecBuilder.java new file mode 100644 index 00000000000..f1f5cdc9d0c --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/TopNMetricSpecBuilder.java @@ -0,0 +1,35 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +/** + */ +public interface TopNMetricSpecBuilder +{ + public void skipTo(String previousStop); + + public void ignoreAfterThreshold(); + + public void ignoreFirstN(int n); + + public void keepOnlyN(int n); + + public T build(); +} diff --git a/processing/src/main/java/io/druid/query/topn/TopNNumericResultBuilder.java b/processing/src/main/java/io/druid/query/topn/TopNNumericResultBuilder.java new file mode 100644 index 00000000000..9f6479baee4 --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/TopNNumericResultBuilder.java @@ -0,0 +1,153 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.google.common.collect.Maps; +import com.google.common.collect.MinMaxPriorityQueue; +import io.druid.query.Result; +import io.druid.query.aggregation.AggregatorFactory; +import io.druid.query.aggregation.PostAggregator; +import io.druid.query.dimension.DimensionSpec; +import org.joda.time.DateTime; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + */ +public class TopNNumericResultBuilder implements TopNResultBuilder +{ + private final DateTime timestamp; + private final DimensionSpec dimSpec; + private final String metricName; + + private MinMaxPriorityQueue pQueue = null; + + public TopNNumericResultBuilder( + DateTime timestamp, + DimensionSpec dimSpec, + String metricName, + int threshold, + final Comparator comparator + ) + { + this.timestamp = timestamp; + this.dimSpec = dimSpec; + this.metricName = metricName; + + instantiatePQueue(threshold, comparator); + } + + @Override + public TopNResultBuilder addEntry( + String dimName, + Object dimValIndex, + Object[] metricVals, + List aggFactories, + List postAggs + ) + { + Map metricValues = Maps.newLinkedHashMap(); + + metricValues.put(dimSpec.getOutputName(), dimName); + + Iterator aggFactoryIter = aggFactories.iterator(); + for (Object metricVal : metricVals) { + metricValues.put(aggFactoryIter.next().getName(), metricVal); + } + for (PostAggregator postAgg : postAggs) { + metricValues.put(postAgg.getName(), postAgg.compute(metricValues)); + } + + Object topNMetricVal = metricValues.get(metricName); + pQueue.add( + new DimValHolder.Builder().withTopNMetricVal(topNMetricVal) + .withDirName(dimName) + .withDimValIndex(dimValIndex) + .withMetricValues(metricValues) + .build() + ); + + return this; + } + + @Override + public TopNResultBuilder addEntry(DimensionAndMetricValueExtractor dimensionAndMetricValueExtractor) + { + pQueue.add( + new DimValHolder.Builder().withTopNMetricVal(dimensionAndMetricValueExtractor.getDimensionValue(metricName)) + .withDirName(dimSpec.getOutputName()) + .withMetricValues(dimensionAndMetricValueExtractor.getBaseObject()) + .build() + ); + + return this; + } + + @Override + public Iterator getTopNIterator() + { + return pQueue.iterator(); + } + + @Override + public Result build() + { + // Pull out top aggregated values + List> values = new ArrayList>(pQueue.size()); + while (!pQueue.isEmpty()) { + values.add(pQueue.remove().getMetricValues()); + } + + return new Result( + timestamp, + new TopNResultValue(values) + ); + } + + private void instantiatePQueue(int threshold, final Comparator comparator) + { + this.pQueue = MinMaxPriorityQueue.orderedBy( + new Comparator() + { + @Override + public int compare(DimValHolder d1, DimValHolder d2) + { + int retVal = comparator.compare(d2.getTopNMetricVal(), d1.getTopNMetricVal()); + + if (retVal == 0) { + if (d1.getDimName() == null) { + retVal = -1; + } else if (d2.getDimName() == null) { + retVal = 1; + } else { + retVal = d1.getDimName().compareTo(d2.getDimName()); + } + } + + return retVal; + } + } + ).maximumSize(threshold).create(); + } +} diff --git a/processing/src/main/java/io/druid/query/topn/TopNParams.java b/processing/src/main/java/io/druid/query/topn/TopNParams.java new file mode 100644 index 00000000000..8ccc85da284 --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/TopNParams.java @@ -0,0 +1,61 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import io.druid.segment.Cursor; +import io.druid.segment.DimensionSelector; + +/** + */ +public class TopNParams +{ + private final DimensionSelector dimSelector; + private final Cursor cursor; + private final int cardinality; + private final int numValuesPerPass; + + protected TopNParams(DimensionSelector dimSelector, Cursor cursor, int cardinality, int numValuesPerPass) + { + this.dimSelector = dimSelector; + this.cursor = cursor; + this.cardinality = cardinality; + this.numValuesPerPass = numValuesPerPass; + } + + public DimensionSelector getDimSelector() + { + return dimSelector; + } + + public Cursor getCursor() + { + return cursor; + } + + public int getCardinality() + { + return cardinality; + } + + public int getNumValuesPerPass() + { + return numValuesPerPass; + } +} diff --git a/processing/src/main/java/io/druid/query/topn/TopNQuery.java b/processing/src/main/java/io/druid/query/topn/TopNQuery.java new file mode 100644 index 00000000000..10b27deb6e3 --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/TopNQuery.java @@ -0,0 +1,211 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import io.druid.granularity.QueryGranularity; +import io.druid.query.BaseQuery; +import io.druid.query.Queries; +import io.druid.query.Result; +import io.druid.query.aggregation.AggregatorFactory; +import io.druid.query.aggregation.PostAggregator; +import io.druid.query.dimension.DimensionSpec; +import io.druid.query.filter.DimFilter; +import io.druid.query.spec.QuerySegmentSpec; + +import java.util.List; +import java.util.Map; + +/** + */ +public class TopNQuery extends BaseQuery> +{ + public static final String TOPN = "topN"; + + private final DimensionSpec dimensionSpec; + private final TopNMetricSpec topNMetricSpec; + private final int threshold; + private final DimFilter dimFilter; + private final QueryGranularity granularity; + private final List aggregatorSpecs; + private final List postAggregatorSpecs; + + @JsonCreator + public TopNQuery( + @JsonProperty("dataSource") String dataSource, + @JsonProperty("dimension") DimensionSpec dimensionSpec, + @JsonProperty("metric") TopNMetricSpec topNMetricSpec, + @JsonProperty("threshold") int threshold, + @JsonProperty("intervals") QuerySegmentSpec querySegmentSpec, + @JsonProperty("filter") DimFilter dimFilter, + @JsonProperty("granularity") QueryGranularity granularity, + @JsonProperty("aggregations") List aggregatorSpecs, + @JsonProperty("postAggregations") List postAggregatorSpecs, + @JsonProperty("context") Map context + ) + { + super(dataSource, querySegmentSpec, context); + this.dimensionSpec = dimensionSpec; + this.topNMetricSpec = topNMetricSpec; + this.threshold = threshold; + + this.dimFilter = dimFilter; + this.granularity = granularity; + this.aggregatorSpecs = aggregatorSpecs; + this.postAggregatorSpecs = postAggregatorSpecs == null ? ImmutableList.of() : postAggregatorSpecs; + + Preconditions.checkNotNull(dimensionSpec, "dimensionSpec can't be null"); + Preconditions.checkNotNull(topNMetricSpec, "must specify a metric"); + + Preconditions.checkArgument(threshold != 0, "Threshold cannot be equal to 0."); + topNMetricSpec.verifyPreconditions(this.aggregatorSpecs, this.postAggregatorSpecs); + + Queries.verifyAggregations(this.aggregatorSpecs, this.postAggregatorSpecs); + } + + @Override + public boolean hasFilters() + { + return dimFilter != null; + } + + @Override + public String getType() + { + return TOPN; + } + + @JsonProperty("dimension") + public DimensionSpec getDimensionSpec() + { + return dimensionSpec; + } + + @JsonProperty("metric") + public TopNMetricSpec getTopNMetricSpec() + { + return topNMetricSpec; + } + + @JsonProperty("threshold") + public int getThreshold() + { + return threshold; + } + + @JsonProperty("filter") + public DimFilter getDimensionsFilter() + { + return dimFilter; + } + + @JsonProperty + public QueryGranularity getGranularity() + { + return granularity; + } + + @JsonProperty("aggregations") + public List getAggregatorSpecs() + { + return aggregatorSpecs; + } + + @JsonProperty("postAggregations") + public List getPostAggregatorSpecs() + { + return postAggregatorSpecs; + } + + public void initTopNAlgorithmSelector(TopNAlgorithmSelector selector) + { + if (dimensionSpec.getDimExtractionFn() != null) { + selector.setHasDimExtractionFn(true); + } + topNMetricSpec.initTopNAlgorithmSelector(selector); + } + + public TopNQuery withQuerySegmentSpec(QuerySegmentSpec querySegmentSpec) + { + return new TopNQuery( + getDataSource(), + dimensionSpec, + topNMetricSpec, + threshold, + querySegmentSpec, + dimFilter, + granularity, + aggregatorSpecs, + postAggregatorSpecs, + getContext() + ); + } + + public TopNQuery withThreshold(int threshold) + { + return new TopNQuery( + getDataSource(), + dimensionSpec, + topNMetricSpec, + threshold, + getQuerySegmentSpec(), + dimFilter, + granularity, + aggregatorSpecs, + postAggregatorSpecs, + getContext() + ); + } + + public TopNQuery withOverriddenContext(Map contextOverrides) + { + return new TopNQuery( + getDataSource(), + dimensionSpec, + topNMetricSpec, + threshold, + getQuerySegmentSpec(), + dimFilter, + granularity, + aggregatorSpecs, + postAggregatorSpecs, + computeOverridenContext(contextOverrides) + ); + } + + @Override + public String toString() + { + return "TopNQuery{" + + "dataSource='" + getDataSource() + '\'' + + ", dimensionSpec=" + dimensionSpec + + ", topNMetricSpec=" + topNMetricSpec + + ", threshold=" + threshold + + ", querySegmentSpec=" + getQuerySegmentSpec() + + ", dimFilter=" + dimFilter + + ", granularity='" + granularity + '\'' + + ", aggregatorSpecs=" + aggregatorSpecs + + ", postAggregatorSpecs=" + postAggregatorSpecs + + '}'; + } +} diff --git a/processing/src/main/java/io/druid/query/topn/TopNQueryBuilder.java b/processing/src/main/java/io/druid/query/topn/TopNQueryBuilder.java new file mode 100644 index 00000000000..1bfb690f490 --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/TopNQueryBuilder.java @@ -0,0 +1,290 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.google.common.collect.Lists; +import io.druid.granularity.QueryGranularity; +import io.druid.query.aggregation.AggregatorFactory; +import io.druid.query.aggregation.PostAggregator; +import io.druid.query.dimension.DefaultDimensionSpec; +import io.druid.query.dimension.DimensionSpec; +import io.druid.query.filter.DimFilter; +import io.druid.query.filter.OrDimFilter; +import io.druid.query.filter.SelectorDimFilter; +import io.druid.query.spec.LegacySegmentSpec; +import io.druid.query.spec.QuerySegmentSpec; +import org.joda.time.Interval; + +import java.util.List; +import java.util.Map; + +/** + * A Builder for TopNQuery. + *

+ * Required: dataSource(), intervals(), metric() and threshold() must be called before build() + * Additional requirement for numeric metric sorts: aggregators() must be called before build() + *

+ * Optional: filters(), granularity(), postAggregators() and context() can be called before build() + *

+ * Usage example: + *


+ *   TopNQuery query = new TopNQueryBuilder()
+ *                                  .dataSource("Example")
+ *                                  .dimension("example_dim")
+ *                                  .metric("example_metric")
+ *                                  .threshold(100)
+ *                                  .intervals("2012-01-01/2012-01-02")
+ *                                  .build();
+ * 
+ * + * @see io.druid.query.topn.TopNQuery + */ +public class TopNQueryBuilder +{ + private String dataSource; + private DimensionSpec dimensionSpec; + private TopNMetricSpec topNMetricSpec; + private int threshold; + private QuerySegmentSpec querySegmentSpec; + private DimFilter dimFilter; + private QueryGranularity granularity; + private List aggregatorSpecs; + private List postAggregatorSpecs; + private Map context; + + public TopNQueryBuilder() + { + dataSource = ""; + dimensionSpec = null; + topNMetricSpec = null; + threshold = 0; + querySegmentSpec = null; + dimFilter = null; + granularity = QueryGranularity.ALL; + aggregatorSpecs = Lists.newArrayList(); + postAggregatorSpecs = Lists.newArrayList(); + context = null; + } + + public String getDataSource() + { + return dataSource; + } + + public DimensionSpec getDimensionSpec() + { + return dimensionSpec; + } + + public TopNMetricSpec getTopNMetricSpec() + { + return topNMetricSpec; + } + + public int getThreshold() + { + return threshold; + } + + public QuerySegmentSpec getQuerySegmentSpec() + { + return querySegmentSpec; + } + + public DimFilter getDimFilter() + { + return dimFilter; + } + + public QueryGranularity getGranularity() + { + return granularity; + } + + public List getAggregatorSpecs() + { + return aggregatorSpecs; + } + + public List getPostAggregatorSpecs() + { + return postAggregatorSpecs; + } + + public Map getContext() + { + return context; + } + + public TopNQuery build() + { + return new TopNQuery( + dataSource, + dimensionSpec, + topNMetricSpec, + threshold, + querySegmentSpec, + dimFilter, + granularity, + aggregatorSpecs, + postAggregatorSpecs, + context + ); + } + + public TopNQueryBuilder copy(TopNQuery query) + { + return new TopNQueryBuilder() + .dataSource(query.getDataSource()) + .dimension(query.getDimensionSpec()) + .metric(query.getTopNMetricSpec()) + .threshold(query.getThreshold()) + .intervals(query.getIntervals()) + .filters(query.getDimensionsFilter()) + .granularity(query.getGranularity()) + .aggregators(query.getAggregatorSpecs()) + .postAggregators(query.getPostAggregatorSpecs()) + .context(query.getContext()); + } + + public TopNQueryBuilder copy(TopNQueryBuilder builder) + { + return new TopNQueryBuilder() + .dataSource(builder.dataSource) + .dimension(builder.dimensionSpec) + .metric(builder.topNMetricSpec) + .threshold(builder.threshold) + .intervals(builder.querySegmentSpec) + .filters(builder.dimFilter) + .granularity(builder.granularity) + .aggregators(builder.aggregatorSpecs) + .postAggregators(builder.postAggregatorSpecs) + .context(builder.context); + } + + public TopNQueryBuilder dataSource(String d) + { + dataSource = d; + return this; + } + + public TopNQueryBuilder dimension(String d) + { + return dimension(d, null); + } + + public TopNQueryBuilder dimension(String d, String outputName) + { + return dimension(new DefaultDimensionSpec(d, outputName)); + } + + public TopNQueryBuilder dimension(DimensionSpec d) + { + dimensionSpec = d; + return this; + } + + public TopNQueryBuilder metric(String s) + { + return metric(new NumericTopNMetricSpec(s)); + } + + public TopNQueryBuilder metric(TopNMetricSpec t) + { + topNMetricSpec = t; + return this; + } + + public TopNQueryBuilder threshold(int i) + { + threshold = i; + return this; + } + + public TopNQueryBuilder intervals(QuerySegmentSpec q) + { + querySegmentSpec = q; + return this; + } + + public TopNQueryBuilder intervals(String s) + { + querySegmentSpec = new LegacySegmentSpec(s); + return this; + } + + public TopNQueryBuilder intervals(List l) + { + querySegmentSpec = new LegacySegmentSpec(l); + return this; + } + + public TopNQueryBuilder filters(String dimensionName, String value) + { + dimFilter = new SelectorDimFilter(dimensionName, value); + return this; + } + + public TopNQueryBuilder filters(String dimensionName, String value, String... values) + { + List fields = Lists.newArrayList(new SelectorDimFilter(dimensionName, value)); + for (String val : values) { + fields.add(new SelectorDimFilter(dimensionName, val)); + } + dimFilter = new OrDimFilter(fields); + return this; + } + + public TopNQueryBuilder filters(DimFilter f) + { + dimFilter = f; + return this; + } + + public TopNQueryBuilder granularity(String g) + { + granularity = QueryGranularity.fromString(g); + return this; + } + + public TopNQueryBuilder granularity(QueryGranularity g) + { + granularity = g; + return this; + } + + public TopNQueryBuilder aggregators(List a) + { + aggregatorSpecs = a; + return this; + } + + public TopNQueryBuilder postAggregators(List p) + { + postAggregatorSpecs = p; + return this; + } + + public TopNQueryBuilder context(Map c) + { + context = c; + return this; + } +} diff --git a/processing/src/main/java/io/druid/query/topn/TopNQueryConfig.java b/processing/src/main/java/io/druid/query/topn/TopNQueryConfig.java new file mode 100644 index 00000000000..32de88f0cb3 --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/TopNQueryConfig.java @@ -0,0 +1,39 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.druid.query.QueryConfig; + +import javax.validation.constraints.Min; + +/** + */ +public class TopNQueryConfig extends QueryConfig +{ + @JsonProperty + @Min(1) + private int minTopNThreshold = 1000; + + public int getMinTopNThreshold() + { + return minTopNThreshold; + } +} diff --git a/processing/src/main/java/io/druid/query/topn/TopNQueryEngine.java b/processing/src/main/java/io/druid/query/topn/TopNQueryEngine.java new file mode 100644 index 00000000000..09a158b31de --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/TopNQueryEngine.java @@ -0,0 +1,117 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import com.metamx.common.guava.FunctionalIterable; +import com.metamx.common.logger.Logger; +import io.druid.collections.StupidPool; +import io.druid.granularity.QueryGranularity; +import io.druid.query.Result; +import io.druid.query.aggregation.AggregatorFactory; +import io.druid.query.filter.Filter; +import io.druid.segment.Capabilities; +import io.druid.segment.Cursor; +import io.druid.segment.StorageAdapter; +import io.druid.segment.filter.Filters; +import org.joda.time.Interval; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + */ +public class TopNQueryEngine +{ + private static final Logger log = new Logger(TopNQueryEngine.class); + + private final StupidPool bufferPool; + + public TopNQueryEngine(StupidPool bufferPool) + { + this.bufferPool = bufferPool; + } + + public Iterable> query(final TopNQuery query, final StorageAdapter adapter) + { + final List queryIntervals = query.getQuerySegmentSpec().getIntervals(); + final Filter filter = Filters.convertDimensionFilters(query.getDimensionsFilter()); + final QueryGranularity granularity = query.getGranularity(); + final Function> mapFn = getMapFn(query, adapter); + + Preconditions.checkArgument( + queryIntervals.size() == 1, "Can only handle a single interval, got[%s]", queryIntervals + ); + + if (mapFn == null) { + return Lists.newArrayList(); + } + + return FunctionalIterable + .create(adapter.makeCursors(filter, queryIntervals.get(0), granularity)) + .transform( + new Function() + { + @Override + public Cursor apply(Cursor input) + { + log.debug("Running over cursor[%s]", adapter.getInterval(), input.getTime()); + return input; + } + } + ) + .keep(mapFn); + } + + private Function> getMapFn(TopNQuery query, final StorageAdapter adapter) + { + if (adapter == null) { + log.warn( + "Null storage adapter found. Probably trying to issue a query against a segment being memory unmapped. Returning empty results." + ); + return null; + } + + final Capabilities capabilities = adapter.getCapabilities(); + final int cardinality = adapter.getDimensionCardinality(query.getDimensionSpec().getDimension()); + int numBytesPerRecord = 0; + for (AggregatorFactory aggregatorFactory : query.getAggregatorSpecs()) { + numBytesPerRecord += aggregatorFactory.getMaxIntermediateSize(); + } + + final TopNAlgorithmSelector selector = new TopNAlgorithmSelector(cardinality, numBytesPerRecord); + query.initTopNAlgorithmSelector(selector); + + TopNAlgorithm topNAlgorithm = null; + if (selector.isHasDimExtractionFn()) { + topNAlgorithm = new DimExtractionTopNAlgorithm(capabilities, query); + } else if (selector.isAggregateAllMetrics()) { + topNAlgorithm = new PooledTopNAlgorithm(capabilities, query, bufferPool); + } else if (selector.isAggregateTopNMetricFirst()) { + topNAlgorithm = new AggregateTopNMetricFirstAlgorithm(capabilities, query, bufferPool); + } else { + topNAlgorithm = new PooledTopNAlgorithm(capabilities, query, bufferPool); + } + + return new TopNMapFn(query, topNAlgorithm); + } +} diff --git a/processing/src/main/java/io/druid/query/topn/TopNQueryQueryToolChest.java b/processing/src/main/java/io/druid/query/topn/TopNQueryQueryToolChest.java new file mode 100644 index 00000000000..6e1d816cbd9 --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/TopNQueryQueryToolChest.java @@ -0,0 +1,405 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Ordering; +import com.google.common.primitives.Ints; +import com.google.inject.Inject; +import com.metamx.common.ISE; +import com.metamx.common.guava.MergeSequence; +import com.metamx.common.guava.Sequence; +import com.metamx.common.guava.Sequences; +import com.metamx.common.guava.nary.BinaryFn; +import com.metamx.emitter.service.ServiceMetricEvent; +import io.druid.collections.OrderedMergeSequence; +import io.druid.granularity.QueryGranularity; +import io.druid.query.CacheStrategy; +import io.druid.query.IntervalChunkingQueryRunner; +import io.druid.query.Query; +import io.druid.query.QueryCacheHelper; +import io.druid.query.QueryRunner; +import io.druid.query.QueryToolChest; +import io.druid.query.Result; +import io.druid.query.ResultGranularTimestampComparator; +import io.druid.query.ResultMergeQueryRunner; +import io.druid.query.aggregation.AggregatorFactory; +import io.druid.query.aggregation.MetricManipulationFn; +import io.druid.query.aggregation.PostAggregator; +import io.druid.query.filter.DimFilter; +import org.joda.time.DateTime; +import org.joda.time.Interval; +import org.joda.time.Minutes; + +import javax.annotation.Nullable; +import java.nio.ByteBuffer; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + */ +public class TopNQueryQueryToolChest extends QueryToolChest, TopNQuery> +{ + private static final byte TOPN_QUERY = 0x1; + + private static final Joiner COMMA_JOIN = Joiner.on(","); + private static final TypeReference> TYPE_REFERENCE = new TypeReference>(){}; + + private static final TypeReference OBJECT_TYPE_REFERENCE = new TypeReference(){}; + private final TopNQueryConfig config; + + @Inject + public TopNQueryQueryToolChest( + TopNQueryConfig config + ) + { + this.config = config; + } + + @Override + public QueryRunner> mergeResults(QueryRunner> runner) + { + return new ResultMergeQueryRunner>(runner) + { + @Override + protected Ordering> makeOrdering(Query> query) + { + return Ordering.from( + new ResultGranularTimestampComparator( + ((TopNQuery) query).getGranularity() + ) + ); + } + + @Override + protected BinaryFn, Result, Result> createMergeFn( + Query> input + ) + { + TopNQuery query = (TopNQuery) input; + return new TopNBinaryFn( + TopNResultMerger.identity, + query.getGranularity(), + query.getDimensionSpec(), + query.getTopNMetricSpec(), + query.getThreshold(), + query.getAggregatorSpecs(), + query.getPostAggregatorSpecs() + ); + } + }; + } + + @Override + public Sequence> mergeSequences(Sequence>> seqOfSequences) + { + return new OrderedMergeSequence>(getOrdering(), seqOfSequences); + } + + @Override + public ServiceMetricEvent.Builder makeMetricBuilder(TopNQuery query) + { + int numMinutes = 0; + for (Interval interval : query.getIntervals()) { + numMinutes += Minutes.minutesIn(interval).getMinutes(); + } + + return new ServiceMetricEvent.Builder() + .setUser2(query.getDataSource()) + .setUser4(String.format("topN/%s/%s", query.getThreshold(), query.getDimensionSpec().getDimension())) + .setUser5(COMMA_JOIN.join(query.getIntervals())) + .setUser6(String.valueOf(query.hasFilters())) + .setUser7(String.format("%,d aggs", query.getAggregatorSpecs().size())) + .setUser9(Minutes.minutes(numMinutes).toString()); + } + + @Override + public Function, Result> makeMetricManipulatorFn( + final TopNQuery query, final MetricManipulationFn fn + ) + { + return new Function, Result>() + { + private String dimension = query.getDimensionSpec().getOutputName(); + + @Override + public Result apply(@Nullable Result result) + { + List> serializedValues = Lists.newArrayList( + Iterables.transform( + result.getValue(), + new Function>() + { + @Override + public Map apply(@Nullable DimensionAndMetricValueExtractor input) + { + final Map values = Maps.newHashMap(); + for (AggregatorFactory agg : query.getAggregatorSpecs()) { + values.put(agg.getName(), fn.manipulate(agg, input.getMetric(agg.getName()))); + } + for (PostAggregator postAgg : query.getPostAggregatorSpecs()) { + values.put(postAgg.getName(), input.getMetric(postAgg.getName())); + } + values.put(dimension, input.getDimensionValue(dimension)); + + return values; + } + } + ) + ); + + return new Result( + result.getTimestamp(), + new TopNResultValue(serializedValues) + ); + } + }; + } + + @Override + public TypeReference> getResultTypeReference() + { + return TYPE_REFERENCE; + } + + @Override + public CacheStrategy, Object, TopNQuery> getCacheStrategy(final TopNQuery query) + { + return new CacheStrategy, Object, TopNQuery>() + { + private final List aggs = query.getAggregatorSpecs(); + private final List postAggs = query.getPostAggregatorSpecs(); + + @Override + public byte[] computeCacheKey(TopNQuery query) + { + final byte[] dimensionSpecBytes = query.getDimensionSpec().getCacheKey(); + final byte[] metricSpecBytes = query.getTopNMetricSpec().getCacheKey(); + + final DimFilter dimFilter = query.getDimensionsFilter(); + final byte[] filterBytes = dimFilter == null ? new byte[]{} : dimFilter.getCacheKey(); + final byte[] aggregatorBytes = QueryCacheHelper.computeAggregatorBytes(query.getAggregatorSpecs()); + final byte[] granularityBytes = query.getGranularity().cacheKey(); + + return ByteBuffer + .allocate( + 1 + dimensionSpecBytes.length + metricSpecBytes.length + 4 + + granularityBytes.length + filterBytes.length + aggregatorBytes.length + ) + .put(TOPN_QUERY) + .put(dimensionSpecBytes) + .put(metricSpecBytes) + .put(Ints.toByteArray(query.getThreshold())) + .put(granularityBytes) + .put(filterBytes) + .put(aggregatorBytes) + .array(); + } + + @Override + public TypeReference getCacheObjectClazz() + { + return OBJECT_TYPE_REFERENCE; + } + + @Override + public Function, Object> prepareForCache() + { + return new Function, Object>() + { + @Override + public Object apply(@Nullable final Result input) + { + List results = Lists.newArrayList(input.getValue()); + final List retVal = Lists.newArrayListWithCapacity(results.size() + 1); + + // make sure to preserve timezone information when caching results + retVal.add(input.getTimestamp().getMillis()); + for (DimensionAndMetricValueExtractor result : results) { + List vals = Lists.newArrayListWithCapacity(aggs.size() + 2); + vals.add(result.getStringDimensionValue(query.getDimensionSpec().getOutputName())); + for (AggregatorFactory agg : aggs) { + vals.add(result.getMetric(agg.getName())); + } + retVal.add(vals); + } + return retVal; + } + }; + } + + @Override + public Function> pullFromCache() + { + return new Function>() + { + private final QueryGranularity granularity = query.getGranularity(); + + @Override + public Result apply(@Nullable Object input) + { + List results = (List) input; + List> retVal = Lists.newArrayListWithCapacity(results.size()); + + Iterator inputIter = results.iterator(); + DateTime timestamp = granularity.toDateTime(new DateTime(inputIter.next()).getMillis()); + + while (inputIter.hasNext()) { + List result = (List) inputIter.next(); + Map vals = Maps.newLinkedHashMap(); + + Iterator aggIter = aggs.iterator(); + Iterator resultIter = result.iterator(); + + vals.put(query.getDimensionSpec().getOutputName(), resultIter.next()); + + while (aggIter.hasNext() && resultIter.hasNext()) { + final AggregatorFactory factory = aggIter.next(); + vals.put(factory.getName(), factory.deserialize(resultIter.next())); + } + + for (PostAggregator postAgg : postAggs) { + vals.put(postAgg.getName(), postAgg.compute(vals)); + } + + retVal.add(vals); + } + + return new Result(timestamp, new TopNResultValue(retVal)); + } + }; + } + + @Override + public Sequence> mergeSequences(Sequence>> seqOfSequences) + { + return new MergeSequence>(getOrdering(), seqOfSequences); + } + }; + } + + @Override + public QueryRunner> preMergeQueryDecoration(QueryRunner> runner) + { + return new IntervalChunkingQueryRunner>(runner, config.getChunkPeriod()); + } + + @Override + public QueryRunner> postMergeQueryDecoration(final QueryRunner> runner) + { + return new ThresholdAdjustingQueryRunner(runner, config.getMinTopNThreshold()); + } + + private static class ThresholdAdjustingQueryRunner implements QueryRunner> + { + private final QueryRunner> runner; + private final int minTopNThreshold; + + public ThresholdAdjustingQueryRunner( + QueryRunner> runner, + int minTopNThreshold + ) + { + this.runner = runner; + this.minTopNThreshold = minTopNThreshold; + } + + @Override + public Sequence> run(Query> input) + { + if (!(input instanceof TopNQuery)) { + throw new ISE("Can only handle [%s], got [%s]", TopNQuery.class, input.getClass()); + } + + final TopNQuery query = (TopNQuery) input; + if (query.getThreshold() > minTopNThreshold) { + return runner.run(query); + } + + final boolean isBySegment = Boolean.parseBoolean(query.getContextValue("bySegment", "false")); + + return Sequences.map( + runner.run(query.withThreshold(minTopNThreshold)), + new Function, Result>() + { + @Override + public Result apply(Result input) + { + if (isBySegment) { + BySegmentTopNResultValue value = (BySegmentTopNResultValue) input.getValue(); + + return new Result( + input.getTimestamp(), + new BySegmentTopNResultValue( + Lists.transform( + value.getResults(), + new Function, Result>() + { + @Override + public Result apply(@Nullable Result input) + { + return new Result( + input.getTimestamp(), + new TopNResultValue( + Lists.newArrayList( + Iterables.limit( + input.getValue(), + query.getThreshold() + ) + ) + ) + ); + } + } + ), + value.getSegmentId(), + value.getIntervalString() + ) + ); + } + + return new Result( + input.getTimestamp(), + new TopNResultValue( + Lists.newArrayList( + Iterables.limit( + input.getValue(), + query.getThreshold() + ) + ) + ) + ); + } + } + ); + } + } + + public Ordering> getOrdering() + { + return Ordering.natural(); + } +} diff --git a/processing/src/main/java/io/druid/query/topn/TopNQueryRunnerFactory.java b/processing/src/main/java/io/druid/query/topn/TopNQueryRunnerFactory.java new file mode 100644 index 00000000000..d342d764d50 --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/TopNQueryRunnerFactory.java @@ -0,0 +1,108 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.google.inject.Inject; +import com.metamx.common.ISE; +import com.metamx.common.guava.BaseSequence; +import com.metamx.common.guava.Sequence; +import io.druid.collections.StupidPool; +import io.druid.guice.annotations.Global; +import io.druid.query.ChainedExecutionQueryRunner; +import io.druid.query.Query; +import io.druid.query.QueryRunner; +import io.druid.query.QueryRunnerFactory; +import io.druid.query.QueryToolChest; +import io.druid.query.Result; +import io.druid.segment.Segment; + +import java.nio.ByteBuffer; +import java.util.Iterator; +import java.util.concurrent.ExecutorService; + +/** + */ +public class TopNQueryRunnerFactory implements QueryRunnerFactory, TopNQuery> +{ + private final StupidPool computationBufferPool; + private final TopNQueryQueryToolChest toolchest; + + @Inject + public TopNQueryRunnerFactory( + @Global StupidPool computationBufferPool, + TopNQueryQueryToolChest toolchest + ) + { + this.computationBufferPool = computationBufferPool; + this.toolchest = toolchest; + } + + @Override + public QueryRunner> createRunner(final Segment segment) + { + final TopNQueryEngine queryEngine = new TopNQueryEngine(computationBufferPool); + return new QueryRunner>() + { + @Override + public Sequence> run(Query> input) + { + if (!(input instanceof TopNQuery)) { + throw new ISE("Got a [%s] which isn't a %s", input.getClass(), TopNQuery.class); + } + + final TopNQuery legacyQuery = (TopNQuery) input; + + return new BaseSequence, Iterator>>( + new BaseSequence.IteratorMaker, Iterator>>() + { + @Override + public Iterator> make() + { + return queryEngine.query(legacyQuery, segment.asStorageAdapter()).iterator(); + } + + @Override + public void cleanup(Iterator> toClean) + { + + } + } + ); + } + }; + + } + + @Override + public QueryRunner> mergeRunners( + ExecutorService queryExecutor, Iterable>> queryRunners + ) + { + return new ChainedExecutionQueryRunner>( + queryExecutor, toolchest.getOrdering(), queryRunners + ); + } + + @Override + public QueryToolChest, TopNQuery> getToolchest() + { + return toolchest; + } +} diff --git a/processing/src/main/java/io/druid/query/topn/TopNResultBuilder.java b/processing/src/main/java/io/druid/query/topn/TopNResultBuilder.java new file mode 100644 index 00000000000..5823ee3eece --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/TopNResultBuilder.java @@ -0,0 +1,48 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import io.druid.query.Result; +import io.druid.query.aggregation.AggregatorFactory; +import io.druid.query.aggregation.PostAggregator; + +import java.util.Iterator; +import java.util.List; + +/** + */ +public interface TopNResultBuilder +{ + public TopNResultBuilder addEntry( + String dimName, + Object dimValIndex, + Object[] metricVals, + List aggFactories, + List postAggs + ); + + public TopNResultBuilder addEntry( + DimensionAndMetricValueExtractor dimensionAndMetricValueExtractor + ); + + public Iterator getTopNIterator(); + + public Result build(); +} diff --git a/processing/src/main/java/io/druid/query/topn/TopNResultMerger.java b/processing/src/main/java/io/druid/query/topn/TopNResultMerger.java new file mode 100644 index 00000000000..f4ff8ca9b06 --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/TopNResultMerger.java @@ -0,0 +1,40 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import io.druid.query.Result; + +import java.util.Comparator; + +/** + */ +public interface TopNResultMerger +{ + public static TopNResultMerger identity = new TopNResultMerger() + { + @Override + public Result getResult(Result result, Comparator comparator) + { + return result; + } + }; + + public Result getResult(Result result, Comparator comparator); +} diff --git a/processing/src/main/java/io/druid/query/topn/TopNResultValue.java b/processing/src/main/java/io/druid/query/topn/TopNResultValue.java new file mode 100644 index 00000000000..b65bb1f815a --- /dev/null +++ b/processing/src/main/java/io/druid/query/topn/TopNResultValue.java @@ -0,0 +1,107 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.base.Function; +import com.google.common.collect.Lists; +import com.metamx.common.IAE; + +import javax.annotation.Nullable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + */ +public class TopNResultValue implements Iterable +{ + private final List value; + + @JsonCreator + public TopNResultValue( + List value + ) + { + this.value = (value == null) ? Lists.newArrayList() : Lists.transform( + value, + new Function() + { + @Override + public DimensionAndMetricValueExtractor apply(@Nullable Object input) + { + if (input instanceof Map) { + return new DimensionAndMetricValueExtractor((Map) input); + } else if (input instanceof DimensionAndMetricValueExtractor) { + return (DimensionAndMetricValueExtractor) input; + } else { + throw new IAE("Unknown type for input[%s]", input.getClass()); + } + } + } + ); + } + + @JsonValue + public List getValue() + { + return value; + } + + @Override + public Iterator iterator() + { + return value.iterator(); + } + + @Override + public String toString() + { + return "TopNResultValue{" + + "value=" + value + + '}'; + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TopNResultValue that = (TopNResultValue) o; + + if (value != null ? !value.equals(that.value) : that.value != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() + { + return value != null ? value.hashCode() : 0; + } +} diff --git a/processing/src/test/java/io/druid/query/TestQueryRunners.java b/processing/src/test/java/io/druid/query/TestQueryRunners.java new file mode 100644 index 00000000000..c4767c1c6f9 --- /dev/null +++ b/processing/src/test/java/io/druid/query/TestQueryRunners.java @@ -0,0 +1,83 @@ +package io.druid.query; + +import com.google.common.base.Supplier; +import io.druid.collections.StupidPool; +import io.druid.query.search.SearchQueryQueryToolChest; +import io.druid.query.search.SearchQueryRunnerFactory; +import io.druid.query.search.search.SearchQueryConfig; +import io.druid.query.timeboundary.TimeBoundaryQueryRunnerFactory; +import io.druid.query.timeseries.TimeseriesQueryRunnerFactory; +import io.druid.query.topn.TopNQueryConfig; +import io.druid.query.topn.TopNQueryQueryToolChest; +import io.druid.query.topn.TopNQueryRunnerFactory; +import io.druid.segment.Segment; + +import java.nio.ByteBuffer; + +/** + */ +public class TestQueryRunners +{ + public static final StupidPool pool = new StupidPool( + new Supplier() + { + @Override + public ByteBuffer get() + { + return ByteBuffer.allocate(1024 * 10); + } + } + ); + + public static final TopNQueryConfig topNConfig = new TopNQueryConfig(); + + public static StupidPool getPool() + { + return pool; + } + + public static QueryRunner makeTopNQueryRunner( + Segment adapter + ) + { + QueryRunnerFactory factory = new TopNQueryRunnerFactory(pool, new TopNQueryQueryToolChest(topNConfig)); + return new FinalizeResultsQueryRunner( + factory.createRunner(adapter), + factory.getToolchest() + ); + } + + public static QueryRunner makeTimeSeriesQueryRunner( + Segment adapter + ) + { + QueryRunnerFactory factory = TimeseriesQueryRunnerFactory.create(); + return new FinalizeResultsQueryRunner( + factory.createRunner(adapter), + factory.getToolchest() + ); + } + + public static QueryRunner makeSearchQueryRunner( + Segment adapter + ) + { + QueryRunnerFactory factory = new SearchQueryRunnerFactory(new SearchQueryQueryToolChest(new SearchQueryConfig())); + return new FinalizeResultsQueryRunner( + factory.createRunner(adapter), + factory.getToolchest() + ); + } + + public static QueryRunner makeTimeBoundaryQueryRunner( + Segment adapter + ) + { + QueryRunnerFactory factory = new TimeBoundaryQueryRunnerFactory(); + return new FinalizeResultsQueryRunner( + factory.createRunner(adapter), + factory.getToolchest() + ); + } + +} \ No newline at end of file diff --git a/processing/src/test/java/io/druid/query/topn/TopNBinaryFnTest.java b/processing/src/test/java/io/druid/query/topn/TopNBinaryFnTest.java new file mode 100644 index 00000000000..2ce63a693e2 --- /dev/null +++ b/processing/src/test/java/io/druid/query/topn/TopNBinaryFnTest.java @@ -0,0 +1,458 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import io.druid.granularity.QueryGranularity; +import io.druid.query.Result; +import io.druid.query.aggregation.AggregatorFactory; +import io.druid.query.aggregation.CountAggregatorFactory; +import io.druid.query.aggregation.LongSumAggregatorFactory; +import io.druid.query.aggregation.PostAggregator; +import io.druid.query.aggregation.post.ArithmeticPostAggregator; +import io.druid.query.aggregation.post.ConstantPostAggregator; +import io.druid.query.aggregation.post.FieldAccessPostAggregator; +import io.druid.query.dimension.DefaultDimensionSpec; +import junit.framework.Assert; +import org.joda.time.DateTime; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + */ +public class TopNBinaryFnTest +{ + final CountAggregatorFactory rowsCount = new CountAggregatorFactory("rows"); + final LongSumAggregatorFactory indexLongSum = new LongSumAggregatorFactory("index", "index"); + final ConstantPostAggregator constant = new ConstantPostAggregator("const", 1L); + final FieldAccessPostAggregator rowsPostAgg = new FieldAccessPostAggregator("rows", "rows"); + final FieldAccessPostAggregator indexPostAgg = new FieldAccessPostAggregator("index", "index"); + final ArithmeticPostAggregator addrowsindexconstant = new ArithmeticPostAggregator( + "addrowsindexconstant", + "+", + Lists.newArrayList(constant, rowsPostAgg, indexPostAgg) + ); + final List aggregatorFactories = Arrays.asList( + rowsCount, + indexLongSum + ); + final List postAggregators = Arrays.asList( + addrowsindexconstant + ); + private final DateTime currTime = new DateTime(); + + private void assertTopNMergeResult(Object o1, Object o2) + { + Iterator i1 = ((Iterable) o1).iterator(); + Iterator i2 = ((Iterable) o2).iterator(); + while (i1.hasNext() && i2.hasNext()) { + Assert.assertEquals(i1.next(), i2.next()); + } + Assert.assertTrue(!i1.hasNext() && !i2.hasNext()); + } + + @Test + public void testMerge() + { + Result result1 = new Result( + currTime, + new TopNResultValue( + ImmutableList.>of( + ImmutableMap.of( + "rows", 1L, + "index", 2L, + "testdim", "1" + ), + ImmutableMap.of( + "rows", 2L, + "index", 4L, + "testdim", "2" + ), + ImmutableMap.of( + "rows", 0L, + "index", 2L, + "testdim", "3" + ) + ) + ) + ); + Result result2 = new Result( + currTime, + new TopNResultValue( + ImmutableList.>of( + ImmutableMap.of( + "rows", 2L, + "index", 3L, + "testdim", "1" + ), + ImmutableMap.of( + "rows", 2L, + "index", 0L, + "testdim", "2" + ), + ImmutableMap.of( + "rows", 0L, + "index", 1L, + "testdim", "3" + ) + ) + ) + ); + + Result expected = new Result( + currTime, + new TopNResultValue( + ImmutableList.>of( + ImmutableMap.of( + "testdim", "1", + "rows", 3L, + "index", 5L, + "addrowsindexconstant", 9.0 + ), + + ImmutableMap.of( + "testdim", "2", + "rows", 4L, + "index", 4L, + "addrowsindexconstant", 9.0 + ) + ) + ) + ); + + Result actual = new TopNBinaryFn( + TopNResultMerger.identity, + QueryGranularity.ALL, + new DefaultDimensionSpec("testdim", null), + new NumericTopNMetricSpec("index"), + 2, + aggregatorFactories, + postAggregators + ).apply( + result1, + result2 + ); + Assert.assertEquals(expected.getTimestamp(), actual.getTimestamp()); + assertTopNMergeResult(expected.getValue(), actual.getValue()); + } + + @Test + public void testMergeDay() + { + Result result1 = new Result( + currTime, + new TopNResultValue( + ImmutableList.>of( + ImmutableMap.of( + "rows", 1L, + "index", 2L, + "testdim", "1" + ), + ImmutableMap.of( + "rows", 2L, + "index", 4L, + "testdim", "2" + ), + ImmutableMap.of( + "rows", 0L, + "index", 2L, + "testdim", "3" + ) + ) + ) + ); + Result result2 = new Result( + currTime, + new TopNResultValue( + ImmutableList.>of( + ImmutableMap.of( + "rows", 2L, + "index", 3L, + "testdim", "1" + ), + ImmutableMap.of( + "rows", 2L, + "index", 0L, + "testdim", "2" + ), + ImmutableMap.of( + "rows", 0L, + "index", 1L, + "testdim", "3" + ) + ) + ) + ); + + Result expected = new Result( + new DateTime(QueryGranularity.DAY.truncate(currTime.getMillis())), + new TopNResultValue( + ImmutableList.>of( + ImmutableMap.of( + "testdim", "1", + "rows", 3L, + "index", 5L, + "addrowsindexconstant", 9.0 + ), + ImmutableMap.of( + "testdim", "2", + "rows", 4L, + "index", 4L, + "addrowsindexconstant", 9.0 + ) + ) + ) + ); + + Result actual = new TopNBinaryFn( + TopNResultMerger.identity, + QueryGranularity.DAY, + new DefaultDimensionSpec("testdim", null), + new NumericTopNMetricSpec("index"), + 2, + aggregatorFactories, + postAggregators + ).apply( + result1, + result2 + ); + Assert.assertEquals(expected.getTimestamp(), actual.getTimestamp()); + assertTopNMergeResult(expected.getValue(), actual.getValue()); + } + + @Test + public void testMergeOneResultNull() + { + Result result1 = new Result( + currTime, + new TopNResultValue( + ImmutableList.>of( + ImmutableMap.of( + "rows", 1L, + "index", 2L, + "testdim", "1" + ), + ImmutableMap.of( + "rows", 2L, + "index", 4L, + "testdim", "2" + ), + ImmutableMap.of( + "rows", 0L, + "index", 2L, + "testdim", "3" + ) + ) + ) + ); + Result result2 = null; + + Result expected = result1; + + Result actual = new TopNBinaryFn( + TopNResultMerger.identity, + QueryGranularity.ALL, + new DefaultDimensionSpec("testdim", null), + new NumericTopNMetricSpec("index"), + 2, + aggregatorFactories, + postAggregators + ).apply( + result1, + result2 + ); + Assert.assertEquals(expected.getTimestamp(), actual.getTimestamp()); + assertTopNMergeResult(expected.getValue(), actual.getValue()); + } + + @Test + public void testMergeByPostAgg() + { + Result result1 = new Result( + currTime, + new TopNResultValue( + ImmutableList.>of( + ImmutableMap.of( + "rows", 1L, + "index", 2L, + "testdim", "1" + ), + ImmutableMap.of( + "rows", 2L, + "index", 4L, + "testdim", "2" + ), + ImmutableMap.of( + "rows", 0L, + "index", 2L, + "testdim", "3" + ) + ) + ) + ); + Result result2 = new Result( + currTime, + new TopNResultValue( + ImmutableList.>of( + ImmutableMap.of( + "rows", 2L, + "index", 3L, + "testdim", "1" + ), + ImmutableMap.of( + "rows", 2L, + "index", 0L, + "testdim", "2" + ), + ImmutableMap.of( + "rows", 0L, + "index", 1L, + "testdim", "3" + ) + ) + ) + ); + + Result expected = new Result( + currTime, + new TopNResultValue( + ImmutableList.>of( + ImmutableMap.of( + "testdim", "1", + "rows", 3L, + "index", 5L, + "addrowsindexconstant", 9.0 + ), + ImmutableMap.of( + "testdim", "2", + "rows", 4L, + "index", 4L, + "addrowsindexconstant", 9.0 + ) + ) + ) + ); + + Result actual = new TopNBinaryFn( + TopNResultMerger.identity, + QueryGranularity.ALL, + new DefaultDimensionSpec("testdim", null), + new NumericTopNMetricSpec("addrowsindexconstant"), + 2, + aggregatorFactories, + postAggregators + ).apply( + result1, + result2 + ); + Assert.assertEquals(expected.getTimestamp(), actual.getTimestamp()); + assertTopNMergeResult(expected.getValue(), actual.getValue()); + } + + @Test + public void testMergeShiftedTimestamp() + { + Result result1 = new Result( + currTime, + new TopNResultValue( + ImmutableList.>of( + ImmutableMap.of( + "rows", 1L, + "index", 2L, + "testdim", "1" + ), + ImmutableMap.of( + "rows", 2L, + "index", 4L, + "testdim", "2" + ), + ImmutableMap.of( + "rows", 0L, + "index", 2L, + "testdim", "3" + ) + ) + ) + ); + Result result2 = new Result( + currTime.plusHours(2), + new TopNResultValue( + ImmutableList.>of( + ImmutableMap.of( + "rows", 2L, + "index", 3L, + "testdim", "1" + ), + ImmutableMap.of( + "rows", 2L, + "index", 0L, + "testdim", "2" + ), + ImmutableMap.of( + "rows", 0L, + "index", 1L, + "testdim", "3" + ) + ) + ) + ); + + Result expected = new Result( + currTime, + new TopNResultValue( + ImmutableList.>of( + ImmutableMap.of( + "testdim", "1", + "rows", 3L, + "index", 5L, + "addrowsindexconstant", 9.0 + ), + ImmutableMap.of( + "testdim", "2", + "rows", 4L, + "index", 4L, + "addrowsindexconstant", 9.0 + ) + ) + ) + ); + + Result actual = new TopNBinaryFn( + TopNResultMerger.identity, + QueryGranularity.ALL, + new DefaultDimensionSpec("testdim", null), + new NumericTopNMetricSpec("index"), + 2, + aggregatorFactories, + postAggregators + ).apply( + result1, + result2 + ); + Assert.assertEquals(expected.getTimestamp(), actual.getTimestamp()); + assertTopNMergeResult(expected.getValue(), actual.getValue()); + } +} diff --git a/processing/src/test/java/io/druid/query/topn/TopNQueryRunnerTest.java b/processing/src/test/java/io/druid/query/topn/TopNQueryRunnerTest.java new file mode 100644 index 00000000000..839f82c3cbc --- /dev/null +++ b/processing/src/test/java/io/druid/query/topn/TopNQueryRunnerTest.java @@ -0,0 +1,1077 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.metamx.common.guava.Sequences; +import io.druid.collections.StupidPool; +import io.druid.granularity.QueryGranularity; +import io.druid.query.Druids; +import io.druid.query.QueryRunner; +import io.druid.query.Result; +import io.druid.query.TestQueryRunners; +import io.druid.query.aggregation.AggregatorFactory; +import io.druid.query.aggregation.CountAggregatorFactory; +import io.druid.query.aggregation.DoubleSumAggregatorFactory; +import io.druid.query.aggregation.LongSumAggregatorFactory; +import io.druid.query.aggregation.MaxAggregatorFactory; +import io.druid.query.aggregation.MinAggregatorFactory; +import io.druid.query.aggregation.PostAggregator; +import io.druid.query.aggregation.post.ArithmeticPostAggregator; +import io.druid.query.aggregation.post.ConstantPostAggregator; +import io.druid.query.aggregation.post.FieldAccessPostAggregator; +import io.druid.query.dimension.ExtractionDimensionSpec; +import io.druid.query.extraction.RegexDimExtractionFn; +import io.druid.query.filter.AndDimFilter; +import io.druid.query.filter.DimFilter; +import io.druid.query.spec.MultipleIntervalSegmentSpec; +import io.druid.query.spec.QuerySegmentSpec; +import io.druid.segment.TestHelper; +import org.joda.time.DateTime; +import org.joda.time.Interval; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + */ +@RunWith(Parameterized.class) +public class TopNQueryRunnerTest +{ + @Parameterized.Parameters + public static Collection constructorFeeder() throws IOException + { + List retVal = Lists.newArrayList(); + retVal.addAll( + TopNQueryRunnerTestHelper.makeQueryRunners( + new TopNQueryRunnerFactory( + TestQueryRunners.getPool(), + new TopNQueryQueryToolChest(new TopNQueryConfig()) + ) + ) + ); + retVal.addAll( + TopNQueryRunnerTestHelper.makeQueryRunners( + new TopNQueryRunnerFactory( + new StupidPool( + new Supplier() + { + @Override + public ByteBuffer get() + { + return ByteBuffer.allocate(2000); + } + } + ), + new TopNQueryQueryToolChest(new TopNQueryConfig()) + ) + ) + ); + + return retVal; + } + + private final QueryRunner runner; + + public TopNQueryRunnerTest( + QueryRunner runner + ) + { + this.runner = runner; + } + + final String dataSource = "testing"; + final QueryGranularity gran = QueryGranularity.DAY; + final QueryGranularity allGran = QueryGranularity.ALL; + final String providerDimension = "provider"; + final String qualityDimension = "quality"; + final String placementishDimension = "placementish"; + final String indexMetric = "index"; + final String addRowsIndexConstantMetric = "addRowsIndexConstant"; + final CountAggregatorFactory rowsCount = new CountAggregatorFactory("rows"); + final LongSumAggregatorFactory indexLongSum = new LongSumAggregatorFactory("index", "index"); + final DoubleSumAggregatorFactory indexDoubleSum = new DoubleSumAggregatorFactory("index", "index"); + final ConstantPostAggregator constant = new ConstantPostAggregator("const", 1L); + final FieldAccessPostAggregator rowsPostAgg = new FieldAccessPostAggregator("rows", "rows"); + final FieldAccessPostAggregator indexPostAgg = new FieldAccessPostAggregator("index", "index"); + final ArithmeticPostAggregator addRowsIndexConstant = + new ArithmeticPostAggregator( + "addRowsIndexConstant", "+", Lists.newArrayList(constant, rowsPostAgg, indexPostAgg) + ); + final List commonAggregators = Arrays.asList(rowsCount, indexDoubleSum); + + + final String[] expectedFullOnIndexValues = new String[]{ + "4500.0", "6077.949111938477", "4922.488838195801", "5726.140853881836", "4698.468170166016", + "4651.030891418457", "4398.145851135254", "4596.068244934082", "4434.630561828613", "0.0", + "6162.801361083984", "5590.292701721191", "4994.298484802246", "5179.679672241211", "6288.556800842285", + "6025.663551330566", "5772.855537414551", "5346.517524719238", "5497.331253051758", "5909.684387207031", + "5862.711364746094", "5958.373008728027", "5224.882194519043", "5456.789611816406", "5456.095397949219", + "4642.481948852539", "5023.572692871094", "5155.821723937988", "5350.3723220825195", "5236.997489929199", + "4910.097717285156", "4507.608840942383", "4659.80500793457", "5354.878845214844", "4945.796455383301", + "6459.080368041992", "4390.493583679199", "6545.758262634277", "6922.801231384277", "6023.452911376953", + "6812.107475280762", "6368.713348388672", "6381.748748779297", "5631.245086669922", "4976.192253112793", + "6541.463027954102", "5983.8513107299805", "5967.189498901367", "5567.139289855957", "4863.5944747924805", + "4681.164360046387", "6122.321441650391", "5410.308860778809", "4846.676376342773", "5333.872688293457", + "5013.053741455078", "4836.85563659668", "5264.486434936523", "4581.821243286133", "4680.233596801758", + "4771.363662719727", "5038.354717254639", "4816.808464050293", "4684.095504760742", "5023.663467407227", + "5889.72257232666", "4984.973915100098", "5664.220512390137", "5572.653915405273", "5537.123138427734", + "5980.422874450684", "6243.834693908691", "5372.147285461426", "5690.728981018066", "5827.796455383301", + "6141.0769119262695", "6082.3237228393555", "5678.771339416504", "6814.467971801758", "6626.151596069336", + "5833.2095947265625", "4679.222328186035", "5367.9403076171875", "5410.445640563965", "5689.197135925293", + "5240.5018310546875", "4790.912239074707", "4992.670921325684", "4796.888023376465", "5479.439590454102", + "5506.567192077637", "4743.144546508789", "4913.282669067383", "4723.869743347168" + }; + + final DateTime skippedDay = new DateTime("2011-01-21T00:00:00.000Z"); + + final QuerySegmentSpec firstToThird = new MultipleIntervalSegmentSpec( + Arrays.asList(new Interval("2011-04-01T00:00:00.000Z/2011-04-03T00:00:00.000Z")) + ); + final QuerySegmentSpec fullOnInterval = new MultipleIntervalSegmentSpec( + Arrays.asList(new Interval("1970-01-01T00:00:00.000Z/2020-01-01T00:00:00.000Z")) + ); + + + @Test + public void testFullOnTopN() + { + TopNQuery query = new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .dimension(providerDimension) + .metric(indexMetric) + .threshold(4) + .intervals(fullOnInterval) + .aggregators( + Lists.newArrayList( + Iterables.concat( + commonAggregators, + Lists.newArrayList( + new MaxAggregatorFactory("maxIndex", "index"), + new MinAggregatorFactory("minIndex", "index") + ) + ) + ) + ) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + List> expectedResults = Arrays.asList( + new Result( + new DateTime("2011-01-12T00:00:00.000Z"), + new TopNResultValue( + Arrays.>asList( + ImmutableMap.builder() + .put("provider", "total_market") + .put("rows", 186L) + .put("index", 215679.82879638672D) + .put("addRowsIndexConstant", 215866.82879638672D) + .put("maxIndex", 1743.9217529296875D) + .put("minIndex", 792.3260498046875D) + .build(), + ImmutableMap.builder() + .put("provider", "upfront") + .put("rows", 186L) + .put("index", 192046.1060180664D) + .put("addRowsIndexConstant", 192233.1060180664D) + .put("maxIndex", 1870.06103515625D) + .put("minIndex", 545.9906005859375D) + .build(), + ImmutableMap.builder() + .put("provider", "spot") + .put("rows", 837L) + .put("index", 95606.57232284546D) + .put("addRowsIndexConstant", 96444.57232284546D) + .put("maxIndex", 277.2735290527344D) + .put("minIndex", 59.02102279663086D) + .build() + ) + ) + ) + ); + + TestHelper.assertExpectedResults(expectedResults, runner.run(query)); + } + + @Test + public void testFullOnTopNOverPostAggs() + { + TopNQuery query = new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .dimension(providerDimension) + .metric(addRowsIndexConstantMetric) + .threshold(4) + .intervals(fullOnInterval) + .aggregators( + Lists.newArrayList( + Iterables.concat( + commonAggregators, + Lists.newArrayList( + new MaxAggregatorFactory("maxIndex", "index"), + new MinAggregatorFactory("minIndex", "index") + ) + ) + ) + ) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + List> expectedResults = Arrays.asList( + new Result( + new DateTime("2011-01-12T00:00:00.000Z"), + new TopNResultValue( + Arrays.>asList( + ImmutableMap.builder() + .put("provider", "total_market") + .put("rows", 186L) + .put("index", 215679.82879638672D) + .put("addRowsIndexConstant", 215866.82879638672D) + .put("maxIndex", 1743.9217529296875D) + .put("minIndex", 792.3260498046875D) + .build(), + ImmutableMap.builder() + .put("provider", "upfront") + .put("rows", 186L) + .put("index", 192046.1060180664D) + .put("addRowsIndexConstant", 192233.1060180664D) + .put("maxIndex", 1870.06103515625D) + .put("minIndex", 545.9906005859375D) + .build(), + ImmutableMap.builder() + .put("provider", "spot") + .put("rows", 837L) + .put("index", 95606.57232284546D) + .put("addRowsIndexConstant", 96444.57232284546D) + .put("maxIndex", 277.2735290527344D) + .put("minIndex", 59.02102279663086D) + .build() + ) + ) + ) + ); + + TestHelper.assertExpectedResults(expectedResults, runner.run(query)); + } + + @Test + public void testTopN() + { + TopNQuery query = new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .dimension(providerDimension) + .metric(indexMetric) + .threshold(4) + .intervals(firstToThird) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + + List> expectedResults = Arrays.asList( + new Result( + new DateTime("2011-04-01T00:00:00.000Z"), + new TopNResultValue( + Arrays.>asList( + ImmutableMap.of( + "provider", "total_market", + "rows", 4L, + "index", 5351.814697265625D, + "addRowsIndexConstant", 5356.814697265625D + ), + ImmutableMap.of( + "provider", "upfront", + "rows", 4L, + "index", 4875.669677734375D, + "addRowsIndexConstant", 4880.669677734375D + ), + ImmutableMap.of( + "provider", "spot", + "rows", 18L, + "index", 2231.8768157958984D, + "addRowsIndexConstant", 2250.8768157958984D + ) + ) + ) + ) + ); + + TestHelper.assertExpectedResults(expectedResults, runner.run(query)); + } + + @Test + public void testTopNWithOrFilter1() + { + TopNQuery query = new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .filters(providerDimension, "total_market", "upfront", "spot") + .dimension(providerDimension) + .metric(indexMetric) + .threshold(4) + .intervals(firstToThird) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + List> expectedResults = Arrays.asList( + new Result( + new DateTime("2011-04-01T00:00:00.000Z"), + new TopNResultValue( + Arrays.>asList( + ImmutableMap.of( + "provider", "total_market", + "rows", 4L, + "index", 5351.814697265625D, + "addRowsIndexConstant", 5356.814697265625D + ), + ImmutableMap.of( + "provider", "upfront", + "rows", 4L, + "index", 4875.669677734375D, + "addRowsIndexConstant", 4880.669677734375D + ), + ImmutableMap.of( + "provider", "spot", + "rows", 18L, + "index", 2231.8768157958984D, + "addRowsIndexConstant", 2250.8768157958984D + ) + ) + ) + ) + ); + + TestHelper.assertExpectedResults(expectedResults, runner.run(query)); + } + + @Test + public void testTopNWithOrFilter2() + { + TopNQuery query = new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .filters(providerDimension, "total_market", "upfront") + .dimension(providerDimension) + .metric(indexMetric) + .threshold(4) + .intervals(firstToThird) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + List> expectedResults = Arrays.asList( + new Result( + new DateTime("2011-04-01T00:00:00.000Z"), + new TopNResultValue( + Arrays.>asList( + ImmutableMap.of( + "provider", "total_market", + "rows", 4L, + "index", 5351.814697265625D, + "addRowsIndexConstant", 5356.814697265625D + ), + ImmutableMap.of( + "provider", "upfront", + "rows", 4L, + "index", 4875.669677734375D, + "addRowsIndexConstant", 4880.669677734375D + ) + ) + ) + ) + ); + + TestHelper.assertExpectedResults(expectedResults, runner.run(query)); + } + + @Test + public void testTopNWithFilter1() + { + TopNQuery query = new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .filters(providerDimension, "upfront") + .dimension(providerDimension) + .metric(indexMetric) + .threshold(4) + .intervals(firstToThird) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + List> expectedResults = Arrays.asList( + new Result( + new DateTime("2011-04-01T00:00:00.000Z"), + new TopNResultValue( + Arrays.>asList( + ImmutableMap.of( + "provider", "upfront", + "rows", 4L, + "index", 4875.669677734375D, + "addRowsIndexConstant", 4880.669677734375D + ) + ) + ) + ) + ); + + TestHelper.assertExpectedResults(expectedResults, runner.run(query)); + } + + @Test + public void testTopNWithFilter2() + { + TopNQuery query = new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .filters(qualityDimension, "mezzanine") + .dimension(providerDimension) + .metric(indexMetric) + .threshold(4) + .intervals(firstToThird) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + List> expectedResults = Arrays.asList( + new Result( + new DateTime("2011-04-01T00:00:00.000Z"), + new TopNResultValue( + Arrays.>asList( + ImmutableMap.of( + "provider", "upfront", + "rows", 2L, + "index", 2591.68359375D, + "addRowsIndexConstant", 2594.68359375D + ), + ImmutableMap.of( + "provider", "total_market", + "rows", 2L, + "index", 2508.39599609375D, + "addRowsIndexConstant", 2511.39599609375D + ), + ImmutableMap.of( + "provider", "spot", + "rows", 2L, + "index", 220.63774871826172D, + "addRowsIndexConstant", 223.63774871826172D + ) + ) + ) + ) + ); + + TestHelper.assertExpectedResults(expectedResults, runner.run(query)); + } + + @Test + public void testTopNWithFilter2OneDay() + { + TopNQuery query = new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .filters(qualityDimension, "mezzanine") + .dimension(providerDimension) + .metric(indexMetric) + .threshold(4) + .intervals( + new MultipleIntervalSegmentSpec( + Arrays.asList(new Interval("2011-04-01T00:00:00.000Z/2011-04-02T00:00:00.000Z")) + ) + ) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + List> expectedResults = Arrays.asList( + new Result( + new DateTime("2011-04-01T00:00:00.000Z"), + new TopNResultValue( + Arrays.>asList( + ImmutableMap.of( + "provider", "upfront", + "rows", 1L, + "index", new Float(1447.341160).doubleValue(), + "addRowsIndexConstant", new Float(1449.341160).doubleValue() + ), + ImmutableMap.of( + "provider", "total_market", + "rows", 1L, + "index", new Float(1314.839715).doubleValue(), + "addRowsIndexConstant", new Float(1316.839715).doubleValue() + ), + ImmutableMap.of( + "provider", "spot", + "rows", 1L, + "index", new Float(109.705815).doubleValue(), + "addRowsIndexConstant", new Float(111.705815).doubleValue() + ) + ) + ) + ) + ); + + TestHelper.assertExpectedResults(expectedResults, runner.run(query)); + } + + @Test + public void testTopNWithNonExistentFilterInOr() + { + TopNQuery query = new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .filters(providerDimension, "total_market", "upfront", "billyblank") + .dimension(providerDimension) + .metric(indexMetric) + .threshold(4) + .intervals(firstToThird) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + List> expectedResults = Arrays.asList( + new Result( + new DateTime("2011-04-01T00:00:00.000Z"), + new TopNResultValue( + Arrays.>asList( + ImmutableMap.of( + "provider", "total_market", + "rows", 4L, + "index", 5351.814697265625D, + "addRowsIndexConstant", 5356.814697265625D + ), + ImmutableMap.of( + "provider", "upfront", + "rows", 4L, + "index", 4875.669677734375D, + "addRowsIndexConstant", 4880.669677734375D + ) + ) + ) + ) + ); + + TestHelper.assertExpectedResults(expectedResults, runner.run(query)); + } + + @Test + public void testTopNWithNonExistentFilter() + { + TopNQuery query = new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .filters(providerDimension, "billyblank") + .dimension(providerDimension) + .metric(indexMetric) + .threshold(4) + .intervals(firstToThird) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + TestHelper.assertExpectedResults( + Lists.>newArrayList( + new Result( + new DateTime("2011-04-01T00:00:00.000Z"), + new TopNResultValue(Lists.>newArrayList()) + ) + ), + runner.run(query) + ); + } + + @Test + public void testTopNWithNonExistentFilterMultiDim() + { + AndDimFilter andDimFilter = Druids.newAndDimFilterBuilder() + .fields( + Lists.newArrayList( + Druids.newSelectorDimFilterBuilder() + .dimension(providerDimension) + .value("billyblank") + .build(), + Druids.newSelectorDimFilterBuilder() + .dimension(qualityDimension) + .value("mezzanine") + .build() + ) + ).build(); + TopNQuery query = new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .filters(andDimFilter) + .dimension(providerDimension) + .metric(indexMetric) + .threshold(4) + .intervals(firstToThird) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + TestHelper.assertExpectedResults( + Lists.>newArrayList( + new Result( + new DateTime("2011-04-01T00:00:00.000Z"), + new TopNResultValue(Lists.>newArrayList()) + ) + ), + runner.run(query) + ); + } + + @Test + public void testTopNWithMultiValueDimFilter1() + { + TopNQuery query = new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .filters(placementishDimension, "m") + .dimension(providerDimension) + .metric(indexMetric) + .threshold(4) + .intervals(firstToThird) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + TestHelper.assertExpectedResults( + Sequences.toList( + runner.run( + new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .filters(qualityDimension, "mezzanine") + .dimension(providerDimension) + .metric(indexMetric) + .threshold(4) + .intervals(firstToThird) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build() + ), Lists.>newArrayList() + ), runner.run(query) + ); + } + + @Test + public void testTopNWithMultiValueDimFilter2() + { + TopNQuery query = new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .filters(placementishDimension, "m", "a", "b") + .dimension(qualityDimension) + .metric(indexMetric) + .threshold(4) + .intervals(firstToThird) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + TestHelper.assertExpectedResults( + Sequences.toList( + runner.run( + new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .filters(qualityDimension, "mezzanine", "automotive", "business") + .dimension(qualityDimension) + .metric(indexMetric) + .threshold(4) + .intervals(firstToThird) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build() + ), Lists.>newArrayList() + ) + , runner.run(query) + ); + } + + @Test + public void testTopNWithMultiValueDimFilter3() + { + TopNQuery query = new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .filters(placementishDimension, "a") + .dimension(placementishDimension) + .metric(indexMetric) + .threshold(4) + .intervals(firstToThird) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + final ArrayList> expectedResults = Lists.newArrayList( + new Result( + new DateTime("2011-04-01T00:00:00.000Z"), + new TopNResultValue( + Arrays.>asList( + ImmutableMap.of( + "placementish", "a", + "rows", 2L, + "index", 283.31103515625D, + "addRowsIndexConstant", 286.31103515625D + ), + ImmutableMap.of( + "placementish", "preferred", + "rows", 2L, + "index", 283.31103515625D, + "addRowsIndexConstant", 286.31103515625D + ) + ) + ) + ) + ); + + TestHelper.assertExpectedResults(expectedResults, runner.run(query)); + } + + @Test + public void testTopNWithMultiValueDimFilter4() + { + TopNQuery query = new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .filters(placementishDimension, "a", "b") + .dimension(placementishDimension) + .metric(indexMetric) + .threshold(4) + .intervals(firstToThird) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + final ArrayList> expectedResults = Lists.newArrayList( + new Result( + new DateTime("2011-04-01T00:00:00.000Z"), + new TopNResultValue( + Arrays.>asList( + ImmutableMap.of( + "placementish", "preferred", + "rows", 4L, + "index", 514.868408203125D, + "addRowsIndexConstant", 519.868408203125D + ), + ImmutableMap.of( + "placementish", + "a", "rows", 2L, + "index", 283.31103515625D, + "addRowsIndexConstant", 286.31103515625D + ), + ImmutableMap.of( + "placementish", "b", + "rows", 2L, + "index", 231.557373046875D, + "addRowsIndexConstant", 234.557373046875D + ) + ) + ) + ) + ); + + TestHelper.assertExpectedResults(expectedResults, runner.run(query)); + } + + @Test + public void testTopNWithMultiValueDimFilter5() + { + TopNQuery query = new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .filters(placementishDimension, "preferred") + .dimension(placementishDimension) + .metric(indexMetric) + .threshold(4) + .intervals(firstToThird) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + final ArrayList> expectedResults = Lists.newArrayList( + new Result( + new DateTime("2011-04-01T00:00:00.000Z"), + new TopNResultValue( + Arrays.>asList( + ImmutableMap.of( + "placementish", "preferred", + "rows", 26L, + "index", 12459.361190795898D, + "addRowsIndexConstant", 12486.361190795898D + ), + ImmutableMap.of( + "placementish", "p", + "rows", 6L, + "index", 5407.213653564453D, + "addRowsIndexConstant", 5414.213653564453D + ), + ImmutableMap.of( + "placementish", "m", + "rows", 6L, + "index", 5320.717338562012D, + "addRowsIndexConstant", 5327.717338562012D + ), + ImmutableMap.of( + "placementish", "t", + "rows", 4L, + "index", 422.3440856933594D, + "addRowsIndexConstant", 427.3440856933594D + ) + ) + ) + ) + ); + + TestHelper.assertExpectedResults(expectedResults, runner.run(query)); + } + + @Test + public void testTopNLexicographic() + { + TopNQuery query = new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .dimension(providerDimension) + .metric(new LexicographicTopNMetricSpec("")) + .threshold(4) + .intervals(firstToThird) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + List> expectedResults = Arrays.asList( + new Result( + new DateTime("2011-04-01T00:00:00.000Z"), + new TopNResultValue( + Arrays.>asList( + ImmutableMap.of( + "provider", "spot", + "rows", 18L, + "index", 2231.8768157958984D, + "addRowsIndexConstant", 2250.8768157958984D + ), + ImmutableMap.of( + "provider", "total_market", + "rows", 4L, + "index", 5351.814697265625D, + "addRowsIndexConstant", 5356.814697265625D + ), + ImmutableMap.of( + "provider", "upfront", + "rows", 4L, + "index", 4875.669677734375D, + "addRowsIndexConstant", 4880.669677734375D + ) + ) + ) + ) + ); + + TestHelper.assertExpectedResults(expectedResults, runner.run(query)); + } + + @Test + public void testTopNLexicographicWithPreviousStop() + { + TopNQuery query = new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .dimension(providerDimension) + .metric(new LexicographicTopNMetricSpec("spot")) + .threshold(4) + .intervals(firstToThird) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + List> expectedResults = Arrays.asList( + new Result( + new DateTime("2011-04-01T00:00:00.000Z"), + new TopNResultValue( + Arrays.>asList( + ImmutableMap.of( + "provider", "total_market", + "rows", 4L, + "index", 5351.814697265625D, + "addRowsIndexConstant", 5356.814697265625D + ), + ImmutableMap.of( + "provider", "upfront", + "rows", 4L, + "index", 4875.669677734375D, + "addRowsIndexConstant", 4880.669677734375D + ) + ) + ) + ) + ); + + TestHelper.assertExpectedResults(expectedResults, runner.run(query)); + } + + @Test + public void testTopNLexicographicWithNonExistingPreviousStop() + { + TopNQuery query = new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .dimension(providerDimension) + .metric(new LexicographicTopNMetricSpec("t")) + .threshold(4) + .intervals(firstToThird) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + List> expectedResults = Arrays.asList( + new Result( + new DateTime("2011-04-01T00:00:00.000Z"), + new TopNResultValue( + Arrays.>asList( + ImmutableMap.of( + "provider", "total_market", + "rows", 4L, + "index", 5351.814697265625D, + "addRowsIndexConstant", 5356.814697265625D + ), + ImmutableMap.of( + "provider", "upfront", + "rows", 4L, + "index", 4875.669677734375D, + "addRowsIndexConstant", 4880.669677734375D + ) + ) + ) + ) + ); + + TestHelper.assertExpectedResults(expectedResults, runner.run(query)); + } + + @Test + public void testTopNDimExtraction() + { + TopNQuery query = new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .dimension( + new ExtractionDimensionSpec( + providerDimension, providerDimension, new RegexDimExtractionFn("(.)") + ) + ) + .metric("rows") + .threshold(4) + .intervals(firstToThird) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + List> expectedResults = Arrays.asList( + new Result( + new DateTime("2011-04-01T00:00:00.000Z"), + new TopNResultValue( + Arrays.>asList( + ImmutableMap.of( + "provider", "s", + "rows", 18L, + "index", 2231.8768157958984D, + "addRowsIndexConstant", 2250.8768157958984D + ), + ImmutableMap.of( + "provider", "t", + "rows", 4L, + "index", 5351.814697265625D, + "addRowsIndexConstant", 5356.814697265625D + ), + ImmutableMap.of( + "provider", "u", + "rows", 4L, + "index", 4875.669677734375D, + "addRowsIndexConstant", 4880.669677734375D + ) + ) + ) + ) + ); + + TestHelper.assertExpectedResults(expectedResults, runner.run(query)); + } + + @Test + public void testInvertedTopNQuery() + { + TopNQuery query = + new TopNQueryBuilder() + .dataSource(dataSource) + .granularity(allGran) + .dimension(providerDimension) + .metric(new InvertedTopNMetricSpec(new NumericTopNMetricSpec(indexMetric))) + .threshold(3) + .intervals(firstToThird) + .aggregators(commonAggregators) + .postAggregators(Arrays.asList(addRowsIndexConstant)) + .build(); + + List> expectedResults = Arrays.asList( + new Result( + new DateTime("2011-04-01T00:00:00.000Z"), + new TopNResultValue( + Arrays.>asList( + ImmutableMap.of( + "provider", "spot", + "rows", 18L, + "index", 2231.8768157958984D, + "addRowsIndexConstant", 2250.8768157958984D + ), + ImmutableMap.of( + "provider", "upfront", + "rows", 4L, + "index", 4875.669677734375D, + "addRowsIndexConstant", 4880.669677734375D + ), + ImmutableMap.of( + "provider", "total_market", + "rows", 4L, + "index", 5351.814697265625D, + "addRowsIndexConstant", 5356.814697265625D + ) + ) + ) + ) + ); + + TestHelper.assertExpectedResults(expectedResults, runner.run(query)); + } +} \ No newline at end of file diff --git a/processing/src/test/java/io/druid/query/topn/TopNQueryRunnerTestHelper.java b/processing/src/test/java/io/druid/query/topn/TopNQueryRunnerTestHelper.java new file mode 100644 index 00000000000..977cd9e7cdf --- /dev/null +++ b/processing/src/test/java/io/druid/query/topn/TopNQueryRunnerTestHelper.java @@ -0,0 +1,73 @@ +/* + * Druid - a distributed column store. + * Copyright (C) 2012, 2013 Metamarkets Group Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.druid.query.topn; + +import io.druid.query.FinalizeResultsQueryRunner; +import io.druid.query.Query; +import io.druid.query.QueryRunner; +import io.druid.query.QueryRunnerFactory; +import io.druid.segment.IncrementalIndexSegment; +import io.druid.segment.QueryableIndex; +import io.druid.segment.QueryableIndexSegment; +import io.druid.segment.Segment; +import io.druid.segment.TestIndex; +import io.druid.segment.incremental.IncrementalIndex; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; + +public class TopNQueryRunnerTestHelper +{ + @SuppressWarnings("unchecked") + public static Collection makeQueryRunners( + QueryRunnerFactory factory + ) + throws IOException + { + final IncrementalIndex rtIndex = TestIndex.getIncrementalTestIndex(); + final QueryableIndex mMappedTestIndex = TestIndex.getMMappedTestIndex(); + final QueryableIndex mergedRealtimeIndex = TestIndex.mergedRealtimeIndex(); + return Arrays.asList( + new Object[][]{ + { + makeQueryRunner(factory, new IncrementalIndexSegment(rtIndex)) + }, + { + makeQueryRunner(factory, new QueryableIndexSegment(null, mMappedTestIndex)) + }, + { + makeQueryRunner(factory, new QueryableIndexSegment(null, mergedRealtimeIndex)) + } + } + ); + } + + public static QueryRunner makeQueryRunner( + QueryRunnerFactory> factory, + Segment adapter + ) + { + return new FinalizeResultsQueryRunner( + factory.createRunner(adapter), + factory.getToolchest() + ); + } +} \ No newline at end of file diff --git a/server/src/main/java/io/druid/guice/QueryRunnerFactoryModule.java b/server/src/main/java/io/druid/guice/QueryRunnerFactoryModule.java index 0f9970a6f30..fc1ab48fcfc 100644 --- a/server/src/main/java/io/druid/guice/QueryRunnerFactoryModule.java +++ b/server/src/main/java/io/druid/guice/QueryRunnerFactoryModule.java @@ -35,6 +35,8 @@ import io.druid.query.timeboundary.TimeBoundaryQuery; import io.druid.query.timeboundary.TimeBoundaryQueryRunnerFactory; import io.druid.query.timeseries.TimeseriesQuery; import io.druid.query.timeseries.TimeseriesQueryRunnerFactory; +import io.druid.query.topn.TopNQuery; +import io.druid.query.topn.TopNQueryRunnerFactory; import java.util.Map; @@ -49,6 +51,7 @@ public class QueryRunnerFactoryModule extends QueryToolChestModule .put(TimeBoundaryQuery.class, TimeBoundaryQueryRunnerFactory.class) .put(SegmentMetadataQuery.class, SegmentMetadataQueryRunnerFactory.class) .put(GroupByQuery.class, GroupByQueryRunnerFactory.class) + .put(TopNQuery.class, TopNQueryRunnerFactory.class) .build(); @Override diff --git a/server/src/main/java/io/druid/guice/QueryToolChestModule.java b/server/src/main/java/io/druid/guice/QueryToolChestModule.java index 7c4981560d8..4e103db29de 100644 --- a/server/src/main/java/io/druid/guice/QueryToolChestModule.java +++ b/server/src/main/java/io/druid/guice/QueryToolChestModule.java @@ -38,6 +38,9 @@ import io.druid.query.timeboundary.TimeBoundaryQuery; import io.druid.query.timeboundary.TimeBoundaryQueryQueryToolChest; import io.druid.query.timeseries.TimeseriesQuery; import io.druid.query.timeseries.TimeseriesQueryQueryToolChest; +import io.druid.query.topn.TopNQuery; +import io.druid.query.topn.TopNQueryConfig; +import io.druid.query.topn.TopNQueryQueryToolChest; import java.util.Map; @@ -52,6 +55,7 @@ public class QueryToolChestModule implements Module .put(TimeBoundaryQuery.class, TimeBoundaryQueryQueryToolChest.class) .put(SegmentMetadataQuery.class, SegmentMetadataQueryQueryToolChest.class) .put(GroupByQuery.class, GroupByQueryQueryToolChest.class) + .put(TopNQuery.class, TopNQueryQueryToolChest.class) .build(); @Override @@ -67,5 +71,6 @@ public class QueryToolChestModule implements Module JsonConfigProvider.bind(binder, "druid.query", QueryConfig.class); JsonConfigProvider.bind(binder, "druid.query.groupBy", GroupByQueryConfig.class); JsonConfigProvider.bind(binder, "druid.query.search", SearchQueryConfig.class); + JsonConfigProvider.bind(binder, "druid.query.topN", TopNQueryConfig.class); } } From 0301dde671a7db828acb4fb9c90652597cacd772 Mon Sep 17 00:00:00 2001 From: fjy Date: Wed, 8 Jan 2014 15:56:29 -0800 Subject: [PATCH 188/189] remove unnecessary doc line --- .../src/main/java/io/druid/query/topn/TopNAlgorithmSelector.java | 1 - 1 file changed, 1 deletion(-) diff --git a/processing/src/main/java/io/druid/query/topn/TopNAlgorithmSelector.java b/processing/src/main/java/io/druid/query/topn/TopNAlgorithmSelector.java index 424a3aa5950..a65b78dbc96 100644 --- a/processing/src/main/java/io/druid/query/topn/TopNAlgorithmSelector.java +++ b/processing/src/main/java/io/druid/query/topn/TopNAlgorithmSelector.java @@ -50,7 +50,6 @@ public class TopNAlgorithmSelector { // These are just heuristics based on an analysis of where an inflection point may lie to switch // between different algorithms - // More info: https://metamarkets.atlassian.net/wiki/display/APP/Top+n+speeds+with+uniques if (cardinality > 400000 && numBytesPerRecord > 100) { this.aggregateTopNMetricFirst = aggregateTopNMetricFirst; } From 90f3056fce38a19c8c672ab1e6260af5530e65a5 Mon Sep 17 00:00:00 2001 From: fjy Date: Wed, 8 Jan 2014 16:10:48 -0800 Subject: [PATCH 189/189] clean up doc --- docs/content/TopNQuery.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/content/TopNQuery.md b/docs/content/TopNQuery.md index e0a0fc37077..d418799d29c 100644 --- a/docs/content/TopNQuery.md +++ b/docs/content/TopNQuery.md @@ -84,31 +84,31 @@ The format of the results would look like so: "timestamp": "2013-08-31T00:00:00.000Z", "result": [ { - "user": "67.173.175.77", + "dim1": "dim1_val", "count": 111, "some_metrics": 10669, "average": 96.11711711711712 }, { - "user": "24.10.49.170", + "dim1": "another_dim1_val", "count": 88, "some_metrics": 28344, "average": 322.09090909090907 }, { - "user": "72.193.24.148", + "dim1": "dim1_val3", "count": 70, "some_metrics": 871, "average": 12.442857142857143 }, { - "user": "108.46.28.47", + "dim1": "dim1_val4", "count": 62, "some_metrics": 815, "average": 13.14516129032258 }, { - "user": "99.181.143.133", + "dim1": "dim1_val5", "count": 60, "some_metrics": 2787, "average": 46.45