You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kafka.apache.org by li...@apache.org on 2018/10/24 08:01:28 UTC

[kafka-site] branch asf-site updated: Add docs for 2.1.0 RC0 (#163)

This is an automated email from the ASF dual-hosted git repository.

lindong pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/kafka-site.git


The following commit(s) were added to refs/heads/asf-site by this push:
     new 9048fe7  Add docs for 2.1.0 RC0 (#163)
9048fe7 is described below

commit 9048fe7a6cc2b57b6ba7630db19e67e87ce4b47a
Author: Dong Lin <li...@gmail.com>
AuthorDate: Wed Oct 24 01:01:23 2018 -0700

    Add docs for 2.1.0 RC0 (#163)
---
 21/.upgrade.html.swp                               |  Bin 0 -> 118784 bytes
 21/api.html                                        |  120 +
 21/configuration.html                              |  292 ++
 21/connect.html                                    |  551 +++
 21/design.html                                     |  626 ++++
 21/documentation.html                              |   83 +
 21/documentation/index.html                        |   18 +
 21/documentation/streams/architecture.html         |   19 +
 21/documentation/streams/core-concepts.html        |   19 +
 .../streams/developer-guide/app-reset-tool.html    |   19 +
 .../streams/developer-guide/config-streams.html    |   19 +
 .../streams/developer-guide/datatypes.html         |   19 +
 .../streams/developer-guide/dsl-api.html           |   19 +
 .../streams/developer-guide/index.html             |   19 +
 .../developer-guide/interactive-queries.html       |   19 +
 .../streams/developer-guide/manage-topics.html     |   19 +
 .../streams/developer-guide/memory-mgmt.html       |   19 +
 .../streams/developer-guide/processor-api.html     |   19 +
 .../streams/developer-guide/running-app.html       |   19 +
 .../streams/developer-guide/security.html          |   19 +
 .../streams/developer-guide/testing.html           |   19 +
 .../streams/developer-guide/write-streams.html     |   19 +
 21/documentation/streams/index.html                |   19 +
 21/documentation/streams/quickstart.html           |   19 +
 21/documentation/streams/tutorial.html             |   19 +
 21/documentation/streams/upgrade-guide.html        |   19 +
 21/ecosystem.html                                  |   18 +
 21/generated/admin_client_config.html              |    0
 21/generated/connect_config.html                   |    0
 21/generated/connect_metrics.html                  |    0
 21/generated/connect_transforms.html               |    0
 21/generated/consumer_config.html                  |    0
 21/generated/consumer_metrics.html                 |    0
 21/generated/kafka_config.html                     |    0
 21/generated/producer_config.html                  |    0
 21/generated/producer_metrics.html                 |    0
 21/generated/protocol_api_keys.html                |    0
 21/generated/protocol_errors.html                  |    0
 21/generated/protocol_messages.html                |    0
 21/generated/protocol_types.html                   |    0
 21/generated/sink_connector_config.html            |    0
 21/generated/source_connector_config.html          |    0
 21/generated/streams_config.html                   |    0
 21/generated/topic_config.html                     |    0
 21/images/consumer-groups.png                      |  Bin 0 -> 26820 bytes
 21/images/icons/NYT.jpg                            |  Bin 0 -> 12605 bytes
 21/images/icons/architecture--white.png            |  Bin 0 -> 812 bytes
 21/images/icons/architecture.png                   |  Bin 0 -> 818 bytes
 21/images/icons/documentation--white.png           |  Bin 0 -> 1758 bytes
 21/images/icons/documentation.png                  |  Bin 0 -> 1642 bytes
 21/images/icons/line.png                           |  Bin 0 -> 676 bytes
 21/images/icons/new-york.png                       |  Bin 0 -> 3381 bytes
 21/images/icons/rabobank.png                       |  Bin 0 -> 3593 bytes
 21/images/icons/tutorials--white.png               |  Bin 0 -> 1047 bytes
 21/images/icons/tutorials.png                      |  Bin 0 -> 985 bytes
 21/images/icons/zalando.png                        |  Bin 0 -> 2716 bytes
 21/images/kafka-apis.png                           |  Bin 0 -> 86640 bytes
 21/images/kafka_log.png                            |  Bin 0 -> 134321 bytes
 21/images/kafka_multidc.png                        |  Bin 0 -> 33959 bytes
 21/images/kafka_multidc_complex.png                |  Bin 0 -> 38559 bytes
 21/images/log_anatomy.png                          |  Bin 0 -> 19579 bytes
 21/images/log_cleaner_anatomy.png                  |  Bin 0 -> 18638 bytes
 21/images/log_compaction.png                       |  Bin 0 -> 41414 bytes
 21/images/log_consumer.png                         |  Bin 0 -> 139658 bytes
 21/images/mirror-maker.png                         |  Bin 0 -> 6579 bytes
 21/images/producer_consumer.png                    |  Bin 0 -> 8691 bytes
 21/images/streams-architecture-overview.jpg        |  Bin 0 -> 420929 bytes
 21/images/streams-architecture-states.jpg          |  Bin 0 -> 147338 bytes
 21/images/streams-architecture-tasks.jpg           |  Bin 0 -> 130435 bytes
 21/images/streams-architecture-threads.jpg         |  Bin 0 -> 153622 bytes
 21/images/streams-architecture-topology.jpg        |  Bin 0 -> 182199 bytes
 21/images/streams-cache-and-commit-interval.png    |  Bin 0 -> 38648 bytes
 21/images/streams-concepts-topology.jpg            |  Bin 0 -> 136983 bytes
 21/images/streams-elastic-scaling-1.png            |  Bin 0 -> 88673 bytes
 21/images/streams-elastic-scaling-2.png            |  Bin 0 -> 91141 bytes
 21/images/streams-elastic-scaling-3.png            |  Bin 0 -> 88604 bytes
 21/images/streams-interactive-queries-01.png       |  Bin 0 -> 80976 bytes
 21/images/streams-interactive-queries-02.png       |  Bin 0 -> 73218 bytes
 21/images/streams-interactive-queries-03.png       |  Bin 0 -> 79879 bytes
 21/images/streams-interactive-queries-api-01.png   |  Bin 0 -> 84438 bytes
 21/images/streams-interactive-queries-api-02.png   |  Bin 0 -> 100725 bytes
 21/images/streams-session-windows-01.png           |  Bin 0 -> 49003 bytes
 21/images/streams-session-windows-02.png           |  Bin 0 -> 55956 bytes
 21/images/streams-stateful_operations.png          |  Bin 0 -> 123213 bytes
 21/images/streams-table-duality-01.png             |  Bin 0 -> 14534 bytes
 21/images/streams-table-duality-02.png             |  Bin 0 -> 56736 bytes
 21/images/streams-table-duality-03.png             |  Bin 0 -> 91331 bytes
 21/images/streams-table-updates-01.png             |  Bin 0 -> 78069 bytes
 21/images/streams-table-updates-02.png             |  Bin 0 -> 91880 bytes
 21/images/streams-time-windows-hopping.png         |  Bin 0 -> 110392 bytes
 21/images/streams-time-windows-tumbling.png        |  Bin 0 -> 63888 bytes
 21/images/streams-welcome.png                      |  Bin 0 -> 80530 bytes
 21/images/tracking_high_level.png                  |  Bin 0 -> 82759 bytes
 21/implementation.html                             |  305 ++
 21/introduction.html                               |  214 ++
 21/js/templateData.js                              |   24 +
 21/migration.html                                  |   34 +
 21/ops.html                                        | 1915 +++++++++++
 21/protocol.html                                   |  219 ++
 21/quickstart.html                                 |  300 ++
 21/security.html                                   | 1348 ++++++++
 21/streams/architecture.html                       |  173 +
 21/streams/core-concepts.html                      |  298 ++
 21/streams/developer-guide/app-reset-tool.html     |  196 ++
 21/streams/developer-guide/config-streams.html     |  825 +++++
 21/streams/developer-guide/datatypes.html          |  239 ++
 21/streams/developer-guide/dsl-api.html            | 3601 ++++++++++++++++++++
 21/streams/developer-guide/index.html              |  105 +
 .../developer-guide/interactive-queries.html       |  529 +++
 21/streams/developer-guide/manage-topics.html      |  129 +
 21/streams/developer-guide/memory-mgmt.html        |  241 ++
 21/streams/developer-guide/processor-api.html      |  472 +++
 21/streams/developer-guide/running-app.html        |  197 ++
 21/streams/developer-guide/security.html           |  185 +
 21/streams/developer-guide/testing.html            |  440 +++
 21/streams/developer-guide/write-streams.html      |  263 ++
 21/streams/index.html                              |  370 ++
 21/streams/quickstart.html                         |  389 +++
 21/streams/tutorial.html                           |  663 ++++
 21/streams/upgrade-guide.html                      |  701 ++++
 21/toc.html                                        |  153 +
 21/upgrade.html                                    | 1047 ++++++
 21/uses.html                                       |   81 +
 123 files changed, 17725 insertions(+)

diff --git a/21/.upgrade.html.swp b/21/.upgrade.html.swp
new file mode 100644
index 0000000..ad08ac6
Binary files /dev/null and b/21/.upgrade.html.swp differ
diff --git a/21/api.html b/21/api.html
new file mode 100644
index 0000000..ea511b4
--- /dev/null
+++ b/21/api.html
@@ -0,0 +1,120 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<script id="api-template" type="text/x-handlebars-template">
+	Kafka includes five core apis:
+	<ol>
+	<li>The <a href="#producerapi">Producer</a> API allows applications to send streams of data to topics in the Kafka cluster.
+	<li>The <a href="#consumerapi">Consumer</a> API allows applications to read streams of data from topics in the Kafka cluster.
+	<li>The <a href="#streamsapi">Streams</a> API allows transforming streams of data from input topics to output topics.
+	<li>The <a href="#connectapi">Connect</a> API allows implementing connectors that continually pull from some source system or application into Kafka or push from Kafka into some sink system or application.
+	<li>The <a href="#adminapi">AdminClient</a> API allows managing and inspecting topics, brokers, and other Kafka objects.
+	</ol>
+
+	Kafka exposes all its functionality over a language independent protocol which has clients available in many programming languages. However only the Java clients are maintained as part of the main Kafka project, the others are available as independent open source projects. A list of non-Java clients is available <a href="https://cwiki.apache.org/confluence/display/KAFKA/Clients">here</a>.
+
+	<h3><a id="producerapi" href="#producerapi">2.1 Producer API</a></h3>
+
+	The Producer API allows applications to send streams of data to topics in the Kafka cluster.
+	<p>
+	Examples showing how to use the producer are given in the
+	<a href="/{{version}}/javadoc/index.html?org/apache/kafka/clients/producer/KafkaProducer.html" title="Kafka {{dotVersion}} Javadoc">javadocs</a>.
+	<p>
+	To use the producer, you can use the following maven dependency:
+
+	<pre class="brush: xml;">
+		&lt;dependency&gt;
+			&lt;groupId&gt;org.apache.kafka&lt;/groupId&gt;
+			&lt;artifactId&gt;kafka-clients&lt;/artifactId&gt;
+			&lt;version&gt;{{fullDotVersion}}&lt;/version&gt;
+		&lt;/dependency&gt;
+	</pre>
+
+	<h3><a id="consumerapi" href="#consumerapi">2.2 Consumer API</a></h3>
+
+	The Consumer API allows applications to read streams of data from topics in the Kafka cluster.
+	<p>
+	Examples showing how to use the consumer are given in the
+	<a href="/{{version}}/javadoc/index.html?org/apache/kafka/clients/consumer/KafkaConsumer.html" title="Kafka {{dotVersion}} Javadoc">javadocs</a>.
+	<p>
+	To use the consumer, you can use the following maven dependency:
+	<pre class="brush: xml;">
+		&lt;dependency&gt;
+			&lt;groupId&gt;org.apache.kafka&lt;/groupId&gt;
+			&lt;artifactId&gt;kafka-clients&lt;/artifactId&gt;
+			&lt;version&gt;{{fullDotVersion}}&lt;/version&gt;
+		&lt;/dependency&gt;
+	</pre>
+
+	<h3><a id="streamsapi" href="#streamsapi">2.3 Streams API</a></h3>
+
+	The <a href="#streamsapi">Streams</a> API allows transforming streams of data from input topics to output topics.
+	<p>
+	Examples showing how to use this library are given in the
+	<a href="/{{version}}/javadoc/index.html?org/apache/kafka/streams/KafkaStreams.html" title="Kafka {{dotVersion}} Javadoc">javadocs</a>
+	<p>
+	Additional documentation on using the Streams API is available <a href="/{{version}}/documentation/streams">here</a>.
+	<p>
+	To use Kafka Streams you can use the following maven dependency:
+
+	<pre class="brush: xml;">
+		&lt;dependency&gt;
+			&lt;groupId&gt;org.apache.kafka&lt;/groupId&gt;
+			&lt;artifactId&gt;kafka-streams&lt;/artifactId&gt;
+			&lt;version&gt;{{fullDotVersion}}&lt;/version&gt;
+		&lt;/dependency&gt;
+	</pre>
+
+	<p>
+	When using Scala you may optionally include the <code>kafka-streams-scala</code> library.  Additional documentation on using the Kafka Streams DSL for Scala is available <a href="/{{version}}/documentation/streams/developer-guide/dsl-api.html#scala-dsl">in the developer guide</a>.
+	<p>
+	To use Kafka Streams DSL for Scala for Scala 2.11 you can use the following maven dependency:
+
+	<pre class="brush: xml;">
+		&lt;dependency&gt;
+			&lt;groupId&gt;org.apache.kafka&lt;/groupId&gt;
+			&lt;artifactId&gt;kafka-streams-scala_2.11&lt;/artifactId&gt;
+			&lt;version&gt;{{fullDotVersion}}&lt;/version&gt;
+		&lt;/dependency&gt;
+	</pre>
+
+	<h3><a id="connectapi" href="#connectapi">2.4 Connect API</a></h3>
+
+	The Connect API allows implementing connectors that continually pull from some source data system into Kafka or push from Kafka into some sink data system.
+	<p>
+	Many users of Connect won't need to use this API directly, though, they can use pre-built connectors without needing to write any code. Additional information on using Connect is available <a href="/documentation.html#connect">here</a>.
+	<p>
+	Those who want to implement custom connectors can see the <a href="/{{version}}/javadoc/index.html?org/apache/kafka/connect" title="Kafka {{dotVersion}} Javadoc">javadoc</a>.
+	<p>
+
+	<h3><a id="adminapi" href="#adminapi">2.5 AdminClient API</a></h3>
+
+	The AdminClient API supports managing and inspecting topics, brokers, acls, and other Kafka objects.
+	<p>
+	To use the AdminClient API, add the following Maven dependency:
+	<pre class="brush: xml;">
+		&lt;dependency&gt;
+			&lt;groupId&gt;org.apache.kafka&lt;/groupId&gt;
+			&lt;artifactId&gt;kafka-clients&lt;/artifactId&gt;
+			&lt;version&gt;{{fullDotVersion}}&lt;/version&gt;
+		&lt;/dependency&gt;
+	</pre>
+	For more information about the AdminClient APIs, see the <a href="/{{version}}/javadoc/index.html?org/apache/kafka/clients/admin/AdminClient.html" title="Kafka {{dotVersion}} Javadoc">javadoc</a>.
+	<p>
+
+</script>
+
+<div class="p-api"></div>
diff --git a/21/configuration.html b/21/configuration.html
new file mode 100644
index 0000000..bde53a7
--- /dev/null
+++ b/21/configuration.html
@@ -0,0 +1,292 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<script id="configuration-template" type="text/x-handlebars-template">
+  Kafka uses key-value pairs in the <a href="http://en.wikipedia.org/wiki/.properties">property file format</a> for configuration. These values can be supplied either from a file or programmatically.
+
+  <h3><a id="brokerconfigs" href="#brokerconfigs">3.1 Broker Configs</a></h3>
+
+  The essential configurations are the following:
+  <ul>
+      <li><code>broker.id</code>
+      <li><code>log.dirs</code>
+      <li><code>zookeeper.connect</code>
+  </ul>
+
+  Topic-level configurations and defaults are discussed in more detail <a href="#topicconfigs">below</a>.
+
+  <!--#include virtual="generated/kafka_config.html" -->
+
+  <p>More details about broker configuration can be found in the scala class <code>kafka.server.KafkaConfig</code>.</p>
+
+  <h4><a id="dynamicbrokerconfigs" href="#dynamicbrokerconfigs">3.1.1 Updating Broker Configs</a></h4>
+  From Kafka version 1.1 onwards, some of the broker configs can be updated without restarting the broker. See the
+  <code>Dynamic Update Mode</code> column in <a href="#brokerconfigs">Broker Configs</a> for the update mode of each broker config.
+  <ul>
+    <li><code>read-only</code>: Requires a broker restart for update</li>
+    <li><code>per-broker</code>: May be updated dynamically for each broker</li>
+    <li><code>cluster-wide</code>: May be updated dynamically as a cluster-wide default. May also be updated as a per-broker value for testing.</li>
+  </ul>
+
+  To alter the current broker configs for broker id 0 (for example, the number of log cleaner threads):
+  <pre class="brush: bash;">
+  &gt; bin/kafka-configs.sh --bootstrap-server localhost:9092 --entity-type brokers --entity-name 0 --alter --add-config log.cleaner.threads=2
+  </pre>
+
+  To describe the current dynamic broker configs for broker id 0:
+  <pre class="brush: bash;">
+  &gt; bin/kafka-configs.sh --bootstrap-server localhost:9092 --entity-type brokers --entity-name 0 --describe
+  </pre>
+
+  To delete a config override and revert to the statically configured or default value for broker id 0 (for example,
+  the number of log cleaner threads):
+  <pre class="brush: bash;">
+  &gt; bin/kafka-configs.sh --bootstrap-server localhost:9092 --entity-type brokers --entity-name 0 --alter --delete-config log.cleaner.threads
+  </pre>
+
+  Some configs may be configured as a cluster-wide default to maintain consistent values across the whole cluster.  All brokers
+  in the cluster will process the cluster default update. For example, to update log cleaner threads on all brokers:
+  <pre class="brush: bash;">
+  &gt; bin/kafka-configs.sh --bootstrap-server localhost:9092 --entity-type brokers --entity-default --alter --add-config log.cleaner.threads=2
+  </pre>
+
+  To describe the currently configured dynamic cluster-wide default configs:
+  <pre class="brush: bash;">
+  &gt; bin/kafka-configs.sh --bootstrap-server localhost:9092 --entity-type brokers --entity-default --describe
+  </pre>
+
+  All configs that are configurable at cluster level may also be configured at per-broker level (e.g. for testing).
+  If a config value is defined at different levels, the following order of precedence is used:
+  <ul>
+  <li>Dynamic per-broker config stored in ZooKeeper</li>
+  <li>Dynamic cluster-wide default config stored in ZooKeeper</li>
+  <li>Static broker config from <code>server.properties</code></li>
+  <li>Kafka default, see <a href="#brokerconfigs">broker configs</a></li>
+  </ul>
+
+  <h5>Updating Password Configs Dynamically</h5>
+  <p>Password config values that are dynamically updated are encrypted before storing in ZooKeeper. The broker config
+  <code>password.encoder.secret</code> must be configured in <code>server.properties</code> to enable dynamic update
+  of password configs. The secret may be different on different brokers.</p>
+  <p>The secret used for password encoding may be rotated with a rolling restart of brokers. The old secret used for encoding
+  passwords currently in ZooKeeper must be provided in the static broker config <code>password.encoder.old.secret</code> and
+  the new secret must be provided in <code>password.encoder.secret</code>. All dynamic password configs stored in ZooKeeper
+  will be re-encoded with the new secret when the broker starts up.</p>
+  <p>In Kafka 1.1.x, all dynamically updated password configs must be provided in every alter request when updating configs
+  using <code>kafka-configs.sh</code> even if the password config is not being altered. This constraint will be removed in
+  a future release.</p>
+
+  <h5>Updating Password Configs in ZooKeeper Before Starting Brokers</h5>
+
+  From Kafka 2.0.0 onwards, <code>kafka-configs.sh</code> enables dynamic broker configs to be updated using ZooKeeper before
+  starting brokers for bootstrapping. This enables all password configs to be stored in encrypted form, avoiding the need for
+  clear passwords in <code>server.properties</code>. The broker config <code>password.encoder.secret</code> must also be specified
+  if any password configs are included in the alter command. Additional encryption parameters may also be specified. Password
+  encoder configs will not be persisted in ZooKeeper. For example, to store SSL key password for listener <code>INTERNAL</code>
+  on broker 0:
+
+  <pre class="brush: bash;">
+  &gt; bin/kafka-configs.sh --zookeeper localhost:2181 --entity-type brokers --entity-name 0 --alter --add-config
+    'listener.name.internal.ssl.key.password=key-password,password.encoder.secret=secret,password.encoder.iterations=8192'
+  </pre>
+
+  The configuration <code>listener.name.internal.ssl.key.password</code> will be persisted in ZooKeeper in encrypted
+  form using the provided encoder configs. The encoder secret and iterations are not persisted in ZooKeeper.
+
+  <h5>Updating SSL Keystore of an Existing Listener</h5>
+  Brokers may be configured with SSL keystores with short validity periods to reduce the risk of compromised certificates.
+  Keystores may be updated dynamically without restarting the broker. The config name must be prefixed with the listener prefix
+  <code>listener.name.{listenerName}.</code> so that only the keystore config of a specific listener is updated.
+  The following configs may be updated in a single alter request at per-broker level:
+  <ul>
+    <li><code>ssl.keystore.type</code></li>
+    <li><code>ssl.keystore.location</code></li>
+    <li><code>ssl.keystore.password</code></li>
+    <li><code>ssl.key.password</code></li>
+  </ul>
+  If the listener is the inter-broker listener, the update is allowed only if the new keystore is trusted by the truststore
+  configured for that listener. For other listeners, no trust validation is performed on the keystore by the broker. Certificates
+  must be signed by the same certificate authority that signed the old certificate to avoid any client authentication failures.
+
+  <h5>Updating SSL Truststore of an Existing Listener</h5>
+  Broker truststores may be updated dynamically without restarting the broker to add or remove certificates.
+  Updated truststore will be used to authenticate new client connections. The config name must be prefixed with the
+  listener prefix <code>listener.name.{listenerName}.</code> so that only the truststore config of a specific listener
+  is updated. The following configs may be updated in a single alter request at per-broker level:
+  <ul>
+    <li><code>ssl.truststore.type</code></li>
+    <li><code>ssl.truststore.location</code></li>
+    <li><code>ssl.truststore.password</code></li>
+  </ul>
+  If the listener is the inter-broker listener, the update is allowed only if the existing keystore for that listener is trusted by
+  the new truststore. For other listeners, no trust validation is performed by the broker before the update. Removal of CA certificates
+  used to sign client certificates from the new truststore can lead to client authentication failures.
+
+  <h5>Updating Default Topic Configuration</h5>
+  Default topic configuration options used by brokers may be updated without broker restart. The configs are applied to topics
+  without a topic config override for the equivalent per-topic config. One or more of these configs may be overridden at
+  cluster-default level used by all brokers.
+  <ul>
+    <li><code>log.segment.bytes</code></li>
+    <li><code>log.roll.ms</code></li>
+    <li><code>log.roll.hours</code></li>
+    <li><code>log.roll.jitter.ms</code></li>
+    <li><code>log.roll.jitter.hours</code></li>
+    <li><code>log.index.size.max.bytes</code></li>
+    <li><code>log.flush.interval.messages</code></li>
+    <li><code>log.flush.interval.ms</code></li>
+    <li><code>log.retention.bytes</code></li>
+    <li><code>log.retention.ms</code></li>
+    <li><code>log.retention.minutes</code></li>
+    <li><code>log.retention.hours</code></li>
+    <li><code>log.index.interval.bytes</code></li>
+    <li><code>log.cleaner.delete.retention.ms</code></li>
+    <li><code>log.cleaner.min.compaction.lag.ms</code></li>
+    <li><code>log.cleaner.min.cleanable.ratio</code></li>
+    <li><code>log.cleanup.policy</code></li>
+    <li><code>log.segment.delete.delay.ms</code></li>
+    <li><code>unclean.leader.election.enable</code></li>
+    <li><code>min.insync.replicas</code></li>
+    <li><code>max.message.bytes</code></li>
+    <li><code>compression.type</code></li>
+    <li><code>log.preallocate</code></li>
+    <li><code>log.message.timestamp.type</code></li>
+    <li><code>log.message.timestamp.difference.max.ms</code></li>
+  </ul>
+
+  From Kafka version 2.0.0 onwards, unclean leader election is automatically enabled by the controller when the config
+  <code>unclean.leader.election.enable</code> is dynamically updated.
+  In Kafka version 1.1.x, changes to <code>unclean.leader.election.enable</code> take effect only when a new controller is elected.
+  Controller re-election may be forced by running:
+
+  <pre class="brush: bash;">
+  &gt; bin/zookeeper-shell.sh localhost
+  rmr /controller
+  </pre>
+
+  <h5>Updating Log Cleaner Configs</h5>
+  Log cleaner configs may be updated dynamically at cluster-default level used by all brokers. The changes take effect
+  on the next iteration of log cleaning. One or more of these configs may be updated:
+  <ul>
+    <li><code>log.cleaner.threads</code></li>
+    <li><code>log.cleaner.io.max.bytes.per.second</code></li>
+    <li><code>log.cleaner.dedupe.buffer.size</code></li>
+    <li><code>log.cleaner.io.buffer.size</code></li>
+    <li><code>log.cleaner.io.buffer.load.factor</code></li>
+    <li><code>log.cleaner.backoff.ms</code></li>
+  </ul>
+
+  <h5>Updating Thread Configs</h5>
+  The size of various thread pools used by the broker may be updated dynamically at cluster-default level used by all brokers.
+  Updates are restricted to the range <code>currentSize / 2</code> to <code>currentSize * 2</code> to ensure that config updates are
+  handled gracefully.
+  <ul>
+    <li><code>num.network.threads</code></li>
+    <li><code>num.io.threads</code></li>
+    <li><code>num.replica.fetchers</code></li>
+    <li><code>num.recovery.threads.per.data.dir</code></li>
+    <li><code>log.cleaner.threads</code></li>
+    <li><code>background.threads</code></li>
+  </ul>
+
+  <h5>Updating ConnectionQuota Configs</h5>
+  The maximum number of connections allowed for a given IP/host by the broker may be updated dynamically at cluster-default level used by all brokers.
+  The changes will apply for new connection creations and the existing connections count will be taken into account by the new limits.
+  <ul>
+    <li><code>max.connections.per.ip</code></li>
+    <li><code>max.connections.per.ip.overrides</code></li>
+  </ul>
+
+  <h5>Adding and Removing Listeners</h5>
+  <p>Listeners may be added or removed dynamically. When a new listener is added, security configs of the listener must be provided
+  as listener configs with the listener prefix <code>listener.name.{listenerName}.</code>. If the new listener uses SASL,
+  the JAAS configuration of the listener must be provided using the JAAS configuration property <code>sasl.jaas.config</code>
+  with the listener and mechanism prefix. See <a href="#security_jaas_broker">JAAS configuration for Kafka brokers</a> for details.</p>
+
+  <p>In Kafka version 1.1.x, the listener used by the inter-broker listener may not be updated dynamically. To update the inter-broker
+  listener to a new listener, the new listener may be added on all brokers without restarting the broker. A rolling restart is then
+  required to update <code>inter.broker.listener.name</code>.</p>
+
+  In addition to all the security configs of new listeners, the following configs may be updated dynamically at per-broker level:
+  <ul>
+    <li><code>listeners</code></li>
+    <li><code>advertised.listeners</code></li>
+    <li><code>listener.security.protocol.map</code></li>
+  </ul>
+  Inter-broker listener must be configured using the static broker configuration <code>inter.broker.listener.name</code>
+  or <code>inter.broker.security.protocol</code>.
+
+  <h3><a id="topicconfigs" href="#topicconfigs">3.2 Topic-Level Configs</a></h3>
+
+  Configurations pertinent to topics have both a server default as well an optional per-topic override. If no per-topic configuration is given the server default is used. The override can be set at topic creation time by giving one or more <code>--config</code> options. This example creates a topic named <i>my-topic</i> with a custom max message size and flush rate:
+  <pre class="brush: bash;">
+  &gt; bin/kafka-topics.sh --zookeeper localhost:2181 --create --topic my-topic --partitions 1
+      --replication-factor 1 --config max.message.bytes=64000 --config flush.messages=1
+  </pre>
+  Overrides can also be changed or set later using the alter configs command. This example updates the max message size for <i>my-topic</i>:
+  <pre class="brush: bash;">
+  &gt; bin/kafka-configs.sh --zookeeper localhost:2181 --entity-type topics --entity-name my-topic
+      --alter --add-config max.message.bytes=128000
+  </pre>
+
+  To check overrides set on the topic you can do
+  <pre class="brush: bash;">
+  &gt; bin/kafka-configs.sh --zookeeper localhost:2181 --entity-type topics --entity-name my-topic --describe
+  </pre>
+
+  To remove an override you can do
+  <pre class="brush: bash;">
+  &gt; bin/kafka-configs.sh --zookeeper localhost:2181  --entity-type topics --entity-name my-topic
+      --alter --delete-config max.message.bytes
+  </pre>
+
+  The following are the topic-level configurations. The server's default configuration for this property is given under the Server Default Property heading. A given server default config value only applies to a topic if it does not have an explicit topic config override.
+
+  <!--#include virtual="generated/topic_config.html" -->
+
+  <h3><a id="producerconfigs" href="#producerconfigs">3.3 Producer Configs</a></h3>
+
+  Below is the configuration of the producer:
+  <!--#include virtual="generated/producer_config.html" -->
+
+  <h3><a id="consumerconfigs" href="#consumerconfigs">3.4 Consumer Configs</a></h3>
+
+  Below is the configuration for the consumer:
+  <!--#include virtual="generated/consumer_config.html" -->
+
+  <h3><a id="connectconfigs" href="#connectconfigs">3.5 Kafka Connect Configs</a></h3>
+  Below is the configuration of the Kafka Connect framework.
+  <!--#include virtual="generated/connect_config.html" -->
+
+  <h4><a id="sourceconnectconfigs" href="#sourceconnectconfigs">3.5.1 Source Connector Configs</a></h4>
+  Below is the configuration of a source connector.
+  <!--#include virtual="generated/source_connector_config.html" -->
+
+  <h4><a id="sinkconnectconfigs" href="#sinkconnectconfigs">3.5.2 Sink Connector Configs</a></h4>
+  Below is the configuration of a sink connector.
+  <!--#include virtual="generated/sink_connector_config.html" -->
+
+  <h3><a id="streamsconfigs" href="#streamsconfigs">3.6 Kafka Streams Configs</a></h3>
+  Below is the configuration of the Kafka Streams client library.
+  <!--#include virtual="generated/streams_config.html" -->
+
+  <h3><a id="adminclientconfigs" href="#adminclientconfigs">3.7 AdminClient Configs</a></h3>
+  Below is the configuration of the Kafka Admin client library.
+  <!--#include virtual="generated/admin_client_config.html" -->
+</script>
+
+<div class="p-configuration"></div>
diff --git a/21/connect.html b/21/connect.html
new file mode 100644
index 0000000..baefc1b
--- /dev/null
+++ b/21/connect.html
@@ -0,0 +1,551 @@
+<!--~
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~-->
+
+<script id="connect-template" type="text/x-handlebars-template">
+    <h3><a id="connect_overview" href="#connect_overview">8.1 Overview</a></h3>
+
+    <p>Kafka Connect is a tool for scalably and reliably streaming data between Apache Kafka and other systems. It makes it simple to quickly define <i>connectors</i> that move large collections of data into and out of Kafka. Kafka Connect can ingest entire databases or collect metrics from all your application servers into Kafka topics, making the data available for stream processing with low latency. An export job can deliver data from Kafka topics into secondary storage and query syst [...]
+
+    <p>Kafka Connect features include:</p>
+    <ul>
+        <li><b>A common framework for Kafka connectors</b> - Kafka Connect standardizes integration of other data systems with Kafka, simplifying connector development, deployment, and management</li>
+        <li><b>Distributed and standalone modes</b> - scale up to a large, centrally managed service supporting an entire organization or scale down to development, testing, and small production deployments</li>
+        <li><b>REST interface</b> - submit and manage connectors to your Kafka Connect cluster via an easy to use REST API</li>
+        <li><b>Automatic offset management</b> - with just a little information from connectors, Kafka Connect can manage the offset commit process automatically so connector developers do not need to worry about this error prone part of connector development</li>
+        <li><b>Distributed and scalable by default</b> - Kafka Connect builds on the existing group management protocol. More workers can be added to scale up a Kafka Connect cluster.</li>
+        <li><b>Streaming/batch integration</b> - leveraging Kafka's existing capabilities, Kafka Connect is an ideal solution for bridging streaming and batch data systems</li>
+    </ul>
+
+    <h3><a id="connect_user" href="#connect_user">8.2 User Guide</a></h3>
+
+    <p>The quickstart provides a brief example of how to run a standalone version of Kafka Connect. This section describes how to configure, run, and manage Kafka Connect in more detail.</p>
+
+    <h4><a id="connect_running" href="#connect_running">Running Kafka Connect</a></h4>
+
+    <p>Kafka Connect currently supports two modes of execution: standalone (single process) and distributed.</p>
+
+    <p>In standalone mode all work is performed in a single process. This configuration is simpler to setup and get started with and may be useful in situations where only one worker makes sense (e.g. collecting log files), but it does not benefit from some of the features of Kafka Connect such as fault tolerance. You can start a standalone process with the following command:</p>
+
+    <pre class="brush: bash;">
+    &gt; bin/connect-standalone.sh config/connect-standalone.properties connector1.properties [connector2.properties ...]
+    </pre>
+
+    <p>The first parameter is the configuration for the worker. This includes settings such as the Kafka connection parameters, serialization format, and how frequently to commit offsets. The provided example should work well with a local cluster running with the default configuration provided by <code>config/server.properties</code>. It will require tweaking to use with a different configuration or production deployment. All workers (both standalone and distributed) require a few configs:</p>
+    <ul>
+        <li><code>bootstrap.servers</code> - List of Kafka servers used to bootstrap connections to Kafka</li>
+        <li><code>key.converter</code> - Converter class used to convert between Kafka Connect format and the serialized form that is written to Kafka. This controls the format of the keys in messages written to or read from Kafka, and since this is independent of connectors it allows any connector to work with any serialization format. Examples of common formats include JSON and Avro.</li>
+        <li><code>value.converter</code> - Converter class used to convert between Kafka Connect format and the serialized form that is written to Kafka. This controls the format of the values in messages written to or read from Kafka, and since this is independent of connectors it allows any connector to work with any serialization format. Examples of common formats include JSON and Avro.</li>
+    </ul>
+
+    <p>The important configuration options specific to standalone mode are:</p>
+    <ul>
+        <li><code>offset.storage.file.filename</code> - File to store offset data in</li>
+    </ul>
+
+    <p>The parameters that are configured here are intended for producers and consumers used by Kafka Connect to access the configuration, offset and status topics. For configuration of Kafka source and Kafka sink tasks, the same parameters can be used but need to be prefixed with <code>consumer.</code> and <code>producer.</code> respectively. The only parameter that is inherited from the worker configuration is <code>bootstrap.servers</code>, which in most cases will be sufficient, sinc [...]
+
+    <p>The remaining parameters are connector configuration files. You may include as many as you want, but all will execute within the same process (on different threads).</p>
+
+    <p>Distributed mode handles automatic balancing of work, allows you to scale up (or down) dynamically, and offers fault tolerance both in the active tasks and for configuration and offset commit data. Execution is very similar to standalone mode:</p>
+
+    <pre class="brush: bash;">
+    &gt; bin/connect-distributed.sh config/connect-distributed.properties
+    </pre>
+
+    <p>The difference is in the class which is started and the configuration parameters which change how the Kafka Connect process decides where to store configurations, how to assign work, and where to store offsets and task statues. In the distributed mode, Kafka Connect stores the offsets, configs and task statuses in Kafka topics. It is recommended to manually create the topics for offset, configs and statuses in order to achieve the desired the number of partitions and replication f [...]
+
+    <p>In particular, the following configuration parameters, in addition to the common settings mentioned above, are critical to set before starting your cluster:</p>
+    <ul>
+        <li><code>group.id</code> (default <code>connect-cluster</code>) - unique name for the cluster, used in forming the Connect cluster group; note that this <b>must not conflict</b> with consumer group IDs</li>
+        <li><code>config.storage.topic</code> (default <code>connect-configs</code>) - topic to use for storing connector and task configurations; note that this should be a single partition, highly replicated, compacted topic. You may need to manually create the topic to ensure the correct configuration as auto created topics may have multiple partitions or be automatically configured for deletion rather than compaction</li>
+        <li><code>offset.storage.topic</code> (default <code>connect-offsets</code>) - topic to use for storing offsets; this topic should have many partitions, be replicated, and be configured for compaction</li>
+        <li><code>status.storage.topic</code> (default <code>connect-status</code>) - topic to use for storing statuses; this topic can have multiple partitions, and should be replicated and configured for compaction</li>
+    </ul>
+
+    <p>Note that in distributed mode the connector configurations are not passed on the command line. Instead, use the REST API described below to create, modify, and destroy connectors.</p>
+
+
+    <h4><a id="connect_configuring" href="#connect_configuring">Configuring Connectors</a></h4>
+
+    <p>Connector configurations are simple key-value mappings. For standalone mode these are defined in a properties file and passed to the Connect process on the command line. In distributed mode, they will be included in the JSON payload for the request that creates (or modifies) the connector.</p>
+
+    <p>Most configurations are connector dependent, so they can't be outlined here. However, there are a few common options:</p>
+
+    <ul>
+        <li><code>name</code> - Unique name for the connector. Attempting to register again with the same name will fail.</li>
+        <li><code>connector.class</code> - The Java class for the connector</li>
+        <li><code>tasks.max</code> - The maximum number of tasks that should be created for this connector. The connector may create fewer tasks if it cannot achieve this level of parallelism.</li>
+        <li><code>key.converter</code> - (optional) Override the default key converter set by the worker.</li>
+        <li><code>value.converter</code> - (optional) Override the default value converter set by the worker.</li>
+    </ul>
+
+    <p>The <code>connector.class</code> config supports several formats: the full name or alias of the class for this connector. If the connector is org.apache.kafka.connect.file.FileStreamSinkConnector, you can either specify this full name or use FileStreamSink or FileStreamSinkConnector to make the configuration a bit shorter.</p>
+
+    <p>Sink connectors also have a few additional options to control their input. Each sink connector must set one of the following:</p>
+    <ul>
+        <li><code>topics</code> - A comma-separated list of topics to use as input for this connector</li>
+        <li><code>topics.regex</code> - A Java regular expression of topics to use as input for this connector</li>
+    </ul>
+
+    <p>For any other options, you should consult the documentation for the connector.</p>
+
+    <h4><a id="connect_transforms" href="#connect_transforms">Transformations</a></h4>
+
+    <p>Connectors can be configured with transformations to make lightweight message-at-a-time modifications. They can be convenient for data massaging and event routing.</p>
+
+    <p>A transformation chain can be specified in the connector configuration.</p>
+
+    <ul>
+        <li><code>transforms</code> - List of aliases for the transformation, specifying the order in which the transformations will be applied.</li>
+        <li><code>transforms.$alias.type</code> - Fully qualified class name for the transformation.</li>
+        <li><code>transforms.$alias.$transformationSpecificConfig</code> Configuration properties for the transformation</li>
+    </ul>
+
+    <p>For example, lets take the built-in file source connector and use a transformation to add a static field.</p>
+
+    <p>Throughout the example we'll use schemaless JSON data format. To use schemaless format, we changed the following two lines in <code>connect-standalone.properties</code> from true to false:</p>
+
+    <pre class="brush: text;">
+        key.converter.schemas.enable
+        value.converter.schemas.enable
+    </pre>
+
+    <p>The file source connector reads each line as a String. We will wrap each line in a Map and then add a second field to identify the origin of the event. To do this, we use two transformations:</p>
+    <ul>
+        <li><b>HoistField</b> to place the input line inside a Map</li>
+        <li><b>InsertField</b> to add the static field. In this example we'll indicate that the record came from a file connector</li>
+    </ul>
+
+    <p>After adding the transformations, <code>connect-file-source.properties</code> file looks as following:</p>
+
+    <pre class="brush: text;">
+        name=local-file-source
+        connector.class=FileStreamSource
+        tasks.max=1
+        file=test.txt
+        topic=connect-test
+        transforms=MakeMap, InsertSource
+        transforms.MakeMap.type=org.apache.kafka.connect.transforms.HoistField$Value
+        transforms.MakeMap.field=line
+        transforms.InsertSource.type=org.apache.kafka.connect.transforms.InsertField$Value
+        transforms.InsertSource.static.field=data_source
+        transforms.InsertSource.static.value=test-file-source
+    </pre>
+
+    <p>All the lines starting with <code>transforms</code> were added for the transformations. You can see the two transformations we created: "InsertSource" and "MakeMap" are aliases that we chose to give the transformations. The transformation types are based on the list of built-in transformations you can see below. Each transformation type has additional configuration: HoistField requires a configuration called "field", which is the name of the field in the map that will include the  [...]
+
+    <p>When we ran the file source connector on my sample file without the transformations, and then read them using <code>kafka-console-consumer.sh</code>, the results were:</p>
+
+    <pre class="brush: text;">
+        "foo"
+        "bar"
+        "hello world"
+   </pre>
+
+    <p>We then create a new file connector, this time after adding the transformations to the configuration file. This time, the results will be:</p>
+
+    <pre class="brush: json;">
+        {"line":"foo","data_source":"test-file-source"}
+        {"line":"bar","data_source":"test-file-source"}
+        {"line":"hello world","data_source":"test-file-source"}
+    </pre>
+
+    <p>You can see that the lines we've read are now part of a JSON map, and there is an extra field with the static value we specified. This is just one example of what you can do with transformations.</p>
+
+    <p>Several widely-applicable data and routing transformations are included with Kafka Connect:</p>
+
+    <ul>
+        <li>InsertField - Add a field using either static data or record metadata</li>
+        <li>ReplaceField - Filter or rename fields</li>
+        <li>MaskField - Replace field with valid null value for the type (0, empty string, etc)</li>
+        <li>ValueToKey</li>
+        <li>HoistField - Wrap the entire event as a single field inside a Struct or a Map</li>
+        <li>ExtractField - Extract a specific field from Struct and Map and include only this field in results</li>
+        <li>SetSchemaMetadata - modify the schema name or version</li>
+        <li>TimestampRouter - Modify the topic of a record based on original topic and timestamp. Useful when using a sink that needs to write to different tables or indexes based on timestamps</li>
+        <li>RegexRouter - modify the topic of a record based on original topic, replacement string and a regular expression</li>
+    </ul>
+
+    <p>Details on how to configure each transformation are listed below:</p>
+
+
+    <!--#include virtual="generated/connect_transforms.html" -->
+
+    <h4><a id="connect_rest" href="#connect_rest">REST API</a></h4>
+
+    <p>Since Kafka Connect is intended to be run as a service, it also provides a REST API for managing connectors. The REST API server can be configured using the <code>listeners</code> configuration option.
+        This field should contain a list of listeners in the following format: <code>protocol://host:port,protocol2://host2:port2</code>. Currently supported protocols are <code>http</code> and <code>https</code>.
+        For example:</p>
+
+    <pre class="brush: text;">
+        listeners=http://localhost:8080,https://localhost:8443
+    </pre>
+
+    <p>By default, if no <code>listeners</code> are specified, the REST server runs on port 8083 using the HTTP protocol. When using HTTPS, the configuration has to include the SSL configuration.
+    By default, it will use the <code>ssl.*</code> settings. In case it is needed to use different configuration for the REST API than for connecting to Kafka brokers, the fields can be prefixed with <code>listeners.https</code>.
+        When using the prefix, only the prefixed options will be used and the <code>ssl.*</code> options without the prefix will be ignored. Following fields can be used to configure HTTPS for the REST API:</p>
+
+    <ul>
+        <li><code>ssl.keystore.location</code></li>
+        <li><code>ssl.keystore.password</code></li>
+        <li><code>ssl.keystore.type</code></li>
+        <li><code>ssl.key.password</code></li>
+        <li><code>ssl.truststore.location</code></li>
+        <li><code>ssl.truststore.password</code></li>
+        <li><code>ssl.truststore.type</code></li>
+        <li><code>ssl.enabled.protocols</code></li>
+        <li><code>ssl.provider</code></li>
+        <li><code>ssl.protocol</code></li>
+        <li><code>ssl.cipher.suites</code></li>
+        <li><code>ssl.keymanager.algorithm</code></li>
+        <li><code>ssl.secure.random.implementation</code></li>
+        <li><code>ssl.trustmanager.algorithm</code></li>
+        <li><code>ssl.endpoint.identification.algorithm</code></li>
+        <li><code>ssl.client.auth</code></li>
+    </ul>
+
+    <p>The REST API is used not only by users to monitor / manage Kafka Connect. It is also used for the Kafka Connect cross-cluster communication. Requests received on the follower nodes REST API will be forwarded to the leader node REST API.
+    In case the URI under which is given host reachable is different from the URI which it listens on, the configuration options <code>rest.advertised.host.name</code>, <code>rest.advertised.port</code> and <code>rest.advertised.listener</code>
+    can be used to change the URI which will be used by the follower nodes to connect with the leader. When using both HTTP and HTTPS listeners, the <code>rest.advertised.listener</code> option can be also used to define which listener
+        will be used for the cross-cluster communication. When using HTTPS for communication between nodes, the same <code>ssl.*</code> or <code>listeners.https</code> options will be used to configure the HTTPS client.</p>
+
+    <p>The following are the currently supported REST API endpoints:</p>
+
+    <ul>
+        <li><code>GET /connectors</code> - return a list of active connectors</li>
+        <li><code>POST /connectors</code> - create a new connector; the request body should be a JSON object containing a string <code>name</code> field and an object <code>config</code> field with the connector configuration parameters</li>
+        <li><code>GET /connectors/{name}</code> - get information about a specific connector</li>
+        <li><code>GET /connectors/{name}/config</code> - get the configuration parameters for a specific connector</li>
+        <li><code>PUT /connectors/{name}/config</code> - update the configuration parameters for a specific connector</li>
+        <li><code>GET /connectors/{name}/status</code> - get current status of the connector, including if it is running, failed, paused, etc., which worker it is assigned to, error information if it has failed, and the state of all its tasks</li>
+        <li><code>GET /connectors/{name}/tasks</code> - get a list of tasks currently running for a connector</li>
+        <li><code>GET /connectors/{name}/tasks/{taskid}/status</code> - get current status of the task, including if it is running, failed, paused, etc., which worker it is assigned to, and error information if it has failed</li>
+        <li><code>PUT /connectors/{name}/pause</code> - pause the connector and its tasks, which stops message processing until the connector is resumed</li>
+        <li><code>PUT /connectors/{name}/resume</code> - resume a paused connector (or do nothing if the connector is not paused)</li>
+        <li><code>POST /connectors/{name}/restart</code> - restart a connector (typically because it has failed)</li>
+        <li><code>POST /connectors/{name}/tasks/{taskId}/restart</code> - restart an individual task (typically because it has failed)</li>
+        <li><code>DELETE /connectors/{name}</code> - delete a connector, halting all tasks and deleting its configuration</li>
+    </ul>
+
+    <p>Kafka Connect also provides a REST API for getting information about connector plugins:</p>
+
+    <ul>
+        <li><code>GET /connector-plugins</code>- return a list of connector plugins installed in the Kafka Connect cluster. Note that the API only checks for connectors on the worker that handles the request, which means you may see inconsistent results, especially during a rolling upgrade if you add new connector jars</li>
+        <li><code>PUT /connector-plugins/{connector-type}/config/validate</code> - validate the provided configuration values against the configuration definition. This API performs per config validation, returns suggested values and error messages during validation.</li>
+    </ul>
+
+    <h3><a id="connect_development" href="#connect_development">8.3 Connector Development Guide</a></h3>
+
+    <p>This guide describes how developers can write new connectors for Kafka Connect to move data between Kafka and other systems. It briefly reviews a few key concepts and then describes how to create a simple connector.</p>
+
+    <h4><a id="connect_concepts" href="#connect_concepts">Core Concepts and APIs</a></h4>
+
+    <h5><a id="connect_connectorsandtasks" href="#connect_connectorsandtasks">Connectors and Tasks</a></h5>
+
+    <p>To copy data between Kafka and another system, users create a <code>Connector</code> for the system they want to pull data from or push data to. Connectors come in two flavors: <code>SourceConnectors</code> import data from another system (e.g. <code>JDBCSourceConnector</code> would import a relational database into Kafka) and <code>SinkConnectors</code> export data (e.g. <code>HDFSSinkConnector</code> would export the contents of a Kafka topic to an HDFS file).</p>
+
+    <p><code>Connectors</code> do not perform any data copying themselves: their configuration describes the data to be copied, and the <code>Connector</code> is responsible for breaking that job into a set of <code>Tasks</code> that can be distributed to workers. These <code>Tasks</code> also come in two corresponding flavors: <code>SourceTask</code> and <code>SinkTask</code>.</p>
+
+    <p>With an assignment in hand, each <code>Task</code> must copy its subset of the data to or from Kafka. In Kafka Connect, it should always be possible to frame these assignments as a set of input and output streams consisting of records with consistent schemas. Sometimes this mapping is obvious: each file in a set of log files can be considered a stream with each parsed line forming a record using the same schema and offsets stored as byte offsets in the file. In other cases it may  [...]
+
+
+    <h5><a id="connect_streamsandrecords" href="#connect_streamsandrecords">Streams and Records</a></h5>
+
+    <p>Each stream should be a sequence of key-value records. Both the keys and values can have complex structure -- many primitive types are provided, but arrays, objects, and nested data structures can be represented as well. The runtime data format does not assume any particular serialization format; this conversion is handled internally by the framework.</p>
+
+    <p>In addition to the key and value, records (both those generated by sources and those delivered to sinks) have associated stream IDs and offsets. These are used by the framework to periodically commit the offsets of data that have been processed so that in the event of failures, processing can resume from the last committed offsets, avoiding unnecessary reprocessing and duplication of events.</p>
+
+    <h5><a id="connect_dynamicconnectors" href="#connect_dynamicconnectors">Dynamic Connectors</a></h5>
+
+    <p>Not all jobs are static, so <code>Connector</code> implementations are also responsible for monitoring the external system for any changes that might require reconfiguration. For example, in the <code>JDBCSourceConnector</code> example, the <code>Connector</code> might assign a set of tables to each <code>Task</code>. When a new table is created, it must discover this so it can assign the new table to one of the <code>Tasks</code> by updating its configuration. When it notices a c [...]
+
+
+    <h4><a id="connect_developing" href="#connect_developing">Developing a Simple Connector</a></h4>
+
+    <p>Developing a connector only requires implementing two interfaces, the <code>Connector</code> and <code>Task</code>. A simple example is included with the source code for Kafka in the <code>file</code> package. This connector is meant for use in standalone mode and has implementations of a <code>SourceConnector</code>/<code>SourceTask</code> to read each line of a file and emit it as a record and a <code>SinkConnector</code>/<code>SinkTask</code> that writes each record to a file.</p>
+
+    <p>The rest of this section will walk through some code to demonstrate the key steps in creating a connector, but developers should also refer to the full example source code as many details are omitted for brevity.</p>
+
+    <h5><a id="connect_connectorexample" href="#connect_connectorexample">Connector Example</a></h5>
+
+    <p>We'll cover the <code>SourceConnector</code> as a simple example. <code>SinkConnector</code> implementations are very similar. Start by creating the class that inherits from <code>SourceConnector</code> and add a couple of fields that will store parsed configuration information (the filename to read from and the topic to send data to):</p>
+
+    <pre class="brush: java;">
+    public class FileStreamSourceConnector extends SourceConnector {
+        private String filename;
+        private String topic;
+    </pre>
+
+    <p>The easiest method to fill in is <code>taskClass()</code>, which defines the class that should be instantiated in worker processes to actually read the data:</p>
+
+    <pre class="brush: java;">
+    @Override
+    public Class&lt;? extends Task&gt; taskClass() {
+        return FileStreamSourceTask.class;
+    }
+    </pre>
+
+    <p>We will define the <code>FileStreamSourceTask</code> class below. Next, we add some standard lifecycle methods, <code>start()</code> and <code>stop()</code></p>:
+
+    <pre class="brush: java;">
+    @Override
+    public void start(Map&lt;String, String&gt; props) {
+        // The complete version includes error handling as well.
+        filename = props.get(FILE_CONFIG);
+        topic = props.get(TOPIC_CONFIG);
+    }
+
+    @Override
+    public void stop() {
+        // Nothing to do since no background monitoring is required.
+    }
+    </pre>
+
+    <p>Finally, the real core of the implementation is in <code>taskConfigs()</code>. In this case we are only
+    handling a single file, so even though we may be permitted to generate more tasks as per the
+    <code>maxTasks</code> argument, we return a list with only one entry:</p>
+
+    <pre class="brush: java;">
+    @Override
+    public List&lt;Map&lt;String, String&gt;&gt; taskConfigs(int maxTasks) {
+        ArrayList&lt;Map&lt;String, String&gt;&gt; configs = new ArrayList&lt;&gt;();
+        // Only one input stream makes sense.
+        Map&lt;String, String&gt; config = new HashMap&lt;&gt;();
+        if (filename != null)
+            config.put(FILE_CONFIG, filename);
+        config.put(TOPIC_CONFIG, topic);
+        configs.add(config);
+        return configs;
+    }
+    </pre>
+
+    <p>Although not used in the example, <code>SourceTask</code> also provides two APIs to commit offsets in the source system: <code>commit</code> and <code>commitRecord</code>. The APIs are provided for source systems which have an acknowledgement mechanism for messages. Overriding these methods allows the source connector to acknowledge messages in the source system, either in bulk or individually, once they have been written to Kafka.
+    The <code>commit</code> API stores the offsets in the source system, up to the offsets that have been returned by <code>poll</code>. The implementation of this API should block until the commit is complete. The <code>commitRecord</code> API saves the offset in the source system for each <code>SourceRecord</code> after it is written to Kafka. As Kafka Connect will record offsets automatically, <code>SourceTask</code>s are not required to implement them. In cases where a connector does [...]
+
+    <p>Even with multiple tasks, this method implementation is usually pretty simple. It just has to determine the number of input tasks, which may require contacting the remote service it is pulling data from, and then divvy them up. Because some patterns for splitting work among tasks are so common, some utilities are provided in <code>ConnectorUtils</code> to simplify these cases.</p>
+
+    <p>Note that this simple example does not include dynamic input. See the discussion in the next section for how to trigger updates to task configs.</p>
+
+    <h5><a id="connect_taskexample" href="#connect_taskexample">Task Example - Source Task</a></h5>
+
+    <p>Next we'll describe the implementation of the corresponding <code>SourceTask</code>. The implementation is short, but too long to cover completely in this guide. We'll use pseudo-code to describe most of the implementation, but you can refer to the source code for the full example.</p>
+
+    <p>Just as with the connector, we need to create a class inheriting from the appropriate base <code>Task</code> class. It also has some standard lifecycle methods:</p>
+
+
+    <pre class="brush: java;">
+    public class FileStreamSourceTask extends SourceTask {
+        String filename;
+        InputStream stream;
+        String topic;
+
+        @Override
+        public void start(Map&lt;String, String&gt; props) {
+            filename = props.get(FileStreamSourceConnector.FILE_CONFIG);
+            stream = openOrThrowError(filename);
+            topic = props.get(FileStreamSourceConnector.TOPIC_CONFIG);
+        }
+
+        @Override
+        public synchronized void stop() {
+            stream.close();
+        }
+    </pre>
+
+    <p>These are slightly simplified versions, but show that these methods should be relatively simple and the only work they should perform is allocating or freeing resources. There are two points to note about this implementation. First, the <code>start()</code> method does not yet handle resuming from a previous offset, which will be addressed in a later section. Second, the <code>stop()</code> method is synchronized. This will be necessary because <code>SourceTasks</code> are given a [...]
+
+    <p>Next, we implement the main functionality of the task, the <code>poll()</code> method which gets events from the input system and returns a <code>List&lt;SourceRecord&gt;</code>:</p>
+
+    <pre class="brush: java;">
+    @Override
+    public List&lt;SourceRecord&gt; poll() throws InterruptedException {
+        try {
+            ArrayList&lt;SourceRecord&gt; records = new ArrayList&lt;&gt;();
+            while (streamValid(stream) &amp;&amp; records.isEmpty()) {
+                LineAndOffset line = readToNextLine(stream);
+                if (line != null) {
+                    Map&lt;String, Object&gt; sourcePartition = Collections.singletonMap("filename", filename);
+                    Map&lt;String, Object&gt; sourceOffset = Collections.singletonMap("position", streamOffset);
+                    records.add(new SourceRecord(sourcePartition, sourceOffset, topic, Schema.STRING_SCHEMA, line));
+                } else {
+                    Thread.sleep(1);
+                }
+            }
+            return records;
+        } catch (IOException e) {
+            // Underlying stream was killed, probably as a result of calling stop. Allow to return
+            // null, and driving thread will handle any shutdown if necessary.
+        }
+        return null;
+    }
+    </pre>
+
+    <p>Again, we've omitted some details, but we can see the important steps: the <code>poll()</code> method is going to be called repeatedly, and for each call it will loop trying to read records from the file. For each line it reads, it also tracks the file offset. It uses this information to create an output <code>SourceRecord</code> with four pieces of information: the source partition (there is only one, the single file being read), source offset (byte offset in the file), output to [...]
+
+    <p>Note that this implementation uses the normal Java <code>InputStream</code> interface and may sleep if data is not available. This is acceptable because Kafka Connect provides each task with a dedicated thread. While task implementations have to conform to the basic <code>poll()</code> interface, they have a lot of flexibility in how they are implemented. In this case, an NIO-based implementation would be more efficient, but this simple approach works, is quick to implement, and i [...]
+
+    <h5><a id="connect_sinktasks" href="#connect_sinktasks">Sink Tasks</a></h5>
+
+    <p>The previous section described how to implement a simple <code>SourceTask</code>. Unlike <code>SourceConnector</code> and <code>SinkConnector</code>, <code>SourceTask</code> and <code>SinkTask</code> have very different interfaces because <code>SourceTask</code> uses a pull interface and <code>SinkTask</code> uses a push interface. Both share the common lifecycle methods, but the <code>SinkTask</code> interface is quite different:</p>
+
+    <pre class="brush: java;">
+    public abstract class SinkTask implements Task {
+        public void initialize(SinkTaskContext context) {
+            this.context = context;
+        }
+
+        public abstract void put(Collection&lt;SinkRecord&gt; records);
+
+        public void flush(Map&lt;TopicPartition, OffsetAndMetadata&gt; currentOffsets) {
+        }
+    </pre>
+
+    <p>The <code>SinkTask</code> documentation contains full details, but this interface is nearly as simple as the <code>SourceTask</code>. The <code>put()</code> method should contain most of the implementation, accepting sets of <code>SinkRecords</code>, performing any required translation, and storing them in the destination system. This method does not need to ensure the data has been fully written to the destination system before returning. In fact, in many cases internal buffering [...]
+
+    <p>The <code>flush()</code> method is used during the offset commit process, which allows tasks to recover from failures and resume from a safe point such that no events will be missed. The method should push any outstanding data to the destination system and then block until the write has been acknowledged. The <code>offsets</code> parameter can often be ignored, but is useful in some cases where implementations want to store offset information in the destination store to provide ex [...]
+    delivery. For example, an HDFS connector could do this and use atomic move operations to make sure the <code>flush()</code> operation atomically commits the data and offsets to a final location in HDFS.</p>
+
+
+    <h5><a id="connect_resuming" href="#connect_resuming">Resuming from Previous Offsets</a></h5>
+
+    <p>The <code>SourceTask</code> implementation included a stream ID (the input filename) and offset (position in the file) with each record. The framework uses this to commit offsets periodically so that in the case of a failure, the task can recover and minimize the number of events that are reprocessed and possibly duplicated (or to resume from the most recent offset if Kafka Connect was stopped gracefully, e.g. in standalone mode or due to a job reconfiguration). This commit proces [...]
+
+    <p>To correctly resume upon startup, the task can use the <code>SourceContext</code> passed into its <code>initialize()</code> method to access the offset data. In <code>initialize()</code>, we would add a bit more code to read the offset (if it exists) and seek to that position:</p>
+
+    <pre class="brush: java;">
+        stream = new FileInputStream(filename);
+        Map&lt;String, Object&gt; offset = context.offsetStorageReader().offset(Collections.singletonMap(FILENAME_FIELD, filename));
+        if (offset != null) {
+            Long lastRecordedOffset = (Long) offset.get("position");
+            if (lastRecordedOffset != null)
+                seekToOffset(stream, lastRecordedOffset);
+        }
+    </pre>
+
+    <p>Of course, you might need to read many keys for each of the input streams. The <code>OffsetStorageReader</code> interface also allows you to issue bulk reads to efficiently load all offsets, then apply them by seeking each input stream to the appropriate position.</p>
+
+    <h4><a id="connect_dynamicio" href="#connect_dynamicio">Dynamic Input/Output Streams</a></h4>
+
+    <p>Kafka Connect is intended to define bulk data copying jobs, such as copying an entire database rather than creating many jobs to copy each table individually. One consequence of this design is that the set of input or output streams for a connector can vary over time.</p>
+
+    <p>Source connectors need to monitor the source system for changes, e.g. table additions/deletions in a database. When they pick up changes, they should notify the framework via the <code>ConnectorContext</code> object that reconfiguration is necessary. For example, in a <code>SourceConnector</code>:</p>
+
+    <pre class="brush: java;">
+        if (inputsChanged())
+            this.context.requestTaskReconfiguration();
+    </pre>
+
+    <p>The framework will promptly request new configuration information and update the tasks, allowing them to gracefully commit their progress before reconfiguring them. Note that in the <code>SourceConnector</code> this monitoring is currently left up to the connector implementation. If an extra thread is required to perform this monitoring, the connector must allocate it itself.</p>
+
+    <p>Ideally this code for monitoring changes would be isolated to the <code>Connector</code> and tasks would not need to worry about them. However, changes can also affect tasks, most commonly when one of their input streams is destroyed in the input system, e.g. if a table is dropped from a database. If the <code>Task</code> encounters the issue before the <code>Connector</code>, which will be common if the <code>Connector</code> needs to poll for changes, the <code>Task</code> will  [...]
+
+    <p><code>SinkConnectors</code> usually only have to handle the addition of streams, which may translate to new entries in their outputs (e.g., a new database table). The framework manages any changes to the Kafka input, such as when the set of input topics changes because of a regex subscription. <code>SinkTasks</code> should expect new input streams, which may require creating new resources in the downstream system, such as a new table in a database. The trickiest situation to handl [...]
+
+    <h4><a id="connect_configs" href="#connect_configs">Connect Configuration Validation</a></h4>
+
+    <p>Kafka Connect allows you to validate connector configurations before submitting a connector to be executed and can provide feedback about errors and recommended values. To take advantage of this, connector developers need to provide an implementation of <code>config()</code> to expose the configuration definition to the framework.</p>
+
+    <p>The following code in <code>FileStreamSourceConnector</code> defines the configuration and exposes it to the framework.</p>
+
+    <pre class="brush: java;">
+        private static final ConfigDef CONFIG_DEF = new ConfigDef()
+            .define(FILE_CONFIG, Type.STRING, Importance.HIGH, "Source filename.")
+            .define(TOPIC_CONFIG, Type.STRING, Importance.HIGH, "The topic to publish data to");
+
+        public ConfigDef config() {
+            return CONFIG_DEF;
+        }
+    </pre>
+
+    <p><code>ConfigDef</code> class is used for specifying the set of expected configurations. For each configuration, you can specify the name, the type, the default value, the documentation, the group information, the order in the group, the width of the configuration value and the name suitable for display in the UI. Plus, you can provide special validation logic used for single configuration validation by overriding the <code>Validator</code> class. Moreover, as there may be dependen [...]
+
+    <p>Also, the <code>validate()</code> method in <code>Connector</code> provides a default validation implementation which returns a list of allowed configurations together with configuration errors and recommended values for each configuration. However, it does not use the recommended values for configuration validation. You may provide an override of the default implementation for customized configuration validation, which may use the recommended values.</p>
+
+    <h4><a id="connect_schemas" href="#connect_schemas">Working with Schemas</a></h4>
+
+    <p>The FileStream connectors are good examples because they are simple, but they also have trivially structured data -- each line is just a string. Almost all practical connectors will need schemas with more complex data formats.</p>
+
+    <p>To create more complex data, you'll need to work with the Kafka Connect <code>data</code> API. Most structured records will need to interact with two classes in addition to primitive types: <code>Schema</code> and <code>Struct</code>.</p>
+
+    <p>The API documentation provides a complete reference, but here is a simple example creating a <code>Schema</code> and <code>Struct</code>:</p>
+
+    <pre class="brush: java;">
+    Schema schema = SchemaBuilder.struct().name(NAME)
+        .field("name", Schema.STRING_SCHEMA)
+        .field("age", Schema.INT_SCHEMA)
+        .field("admin", new SchemaBuilder.boolean().defaultValue(false).build())
+        .build();
+
+    Struct struct = new Struct(schema)
+        .put("name", "Barbara Liskov")
+        .put("age", 75);
+    </pre>
+
+    <p>If you are implementing a source connector, you'll need to decide when and how to create schemas. Where possible, you should avoid recomputing them as much as possible. For example, if your connector is guaranteed to have a fixed schema, create it statically and reuse a single instance.</p>
+
+    <p>However, many connectors will have dynamic schemas. One simple example of this is a database connector. Considering even just a single table, the schema will not be predefined for the entire connector (as it varies from table to table). But it also may not be fixed for a single table over the lifetime of the connector since the user may execute an <code>ALTER TABLE</code> command. The connector must be able to detect these changes and react appropriately.</p>
+
+    <p>Sink connectors are usually simpler because they are consuming data and therefore do not need to create schemas. However, they should take just as much care to validate that the schemas they receive have the expected format. When the schema does not match -- usually indicating the upstream producer is generating invalid data that cannot be correctly translated to the destination system -- sink connectors should throw an exception to indicate this error to the system.</p>
+
+    <h4><a id="connect_administration" href="#connect_administration">Kafka Connect Administration</a></h4>
+
+    <p>
+    Kafka Connect's <a href="#connect_rest">REST layer</a> provides a set of APIs to enable administration of the cluster. This includes APIs to view the configuration of connectors and the status of their tasks, as well as to alter their current behavior (e.g. changing configuration and restarting tasks).
+    </p>
+
+    <p>
+    When a connector is first submitted to the cluster, the workers rebalance the full set of connectors in the cluster and their tasks so that each worker has approximately the same amount of work. This same rebalancing procedure is also used when connectors increase or decrease the number of tasks they require, or when a connector's configuration is changed. You can use the REST API to view the current status of a connector and its tasks, including the id of the worker to which each wa [...]
+    </p>
+
+    <pre class="brush: json;">
+    {
+    "name": "file-source",
+    "connector": {
+        "state": "RUNNING",
+        "worker_id": "192.168.1.208:8083"
+    },
+    "tasks": [
+        {
+        "id": 0,
+        "state": "RUNNING",
+        "worker_id": "192.168.1.209:8083"
+        }
+    ]
+    }
+    </pre>
+
+    <p>
+    Connectors and their tasks publish status updates to a shared topic (configured with <code>status.storage.topic</code>) which all workers in the cluster monitor. Because the workers consume this topic asynchronously, there is typically a (short) delay before a state change is visible through the status API. The following states are possible for a connector or one of its tasks:
+    </p>
+
+    <ul>
+    <li><b>UNASSIGNED:</b> The connector/task has not yet been assigned to a worker.</li>
+    <li><b>RUNNING:</b> The connector/task is running.</li>
+    <li><b>PAUSED:</b> The connector/task has been administratively paused.</li>
+    <li><b>FAILED:</b> The connector/task has failed (usually by raising an exception, which is reported in the status output).</li>
+    </ul>
+
+    <p>
+    In most cases, connector and task states will match, though they may be different for short periods of time when changes are occurring or if tasks have failed. For example, when a connector is first started, there may be a noticeable delay before the connector and its tasks have all transitioned to the RUNNING state. States will also diverge when tasks fail since Connect does not automatically restart failed tasks. To restart a connector/task manually, you can use the restart APIs li [...]
+    </p>
+
+    <p>
+    It's sometimes useful to temporarily stop the message processing of a connector. For example, if the remote system is undergoing maintenance, it would be preferable for source connectors to stop polling it for new data instead of filling logs with exception spam. For this use case, Connect offers a pause/resume API. While a source connector is paused, Connect will stop polling it for additional records. While a sink connector is paused, Connect will stop pushing new messages to it. T [...]
+    </p>
+</script>
+
+<div class="p-connect"></div>
diff --git a/21/design.html b/21/design.html
new file mode 100644
index 0000000..0061a53
--- /dev/null
+++ b/21/design.html
@@ -0,0 +1,626 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<script id="design-template" type="text/x-handlebars-template">
+    <h3><a id="majordesignelements" href="#majordesignelements">4.1 Motivation</a></h3>
+    <p>
+    We designed Kafka to be able to act as a unified platform for handling all the real-time data feeds <a href="#introduction">a large company might have</a>. To do this we had to think through a fairly broad set of use cases.
+    <p>
+    It would have to have high-throughput to support high volume event streams such as real-time log aggregation.
+    <p>
+    It would need to deal gracefully with large data backlogs to be able to support periodic data loads from offline systems.
+    <p>
+    It also meant the system would have to handle low-latency delivery to handle more traditional messaging use-cases.
+    <p>
+    We wanted to support partitioned, distributed, real-time processing of these feeds to create new, derived feeds. This motivated our partitioning and consumer model.
+    <p>
+    Finally in cases where the stream is fed into other data systems for serving, we knew the system would have to be able to guarantee fault-tolerance in the presence of machine failures.
+    <p>
+    Supporting these uses led us to a design with a number of unique elements, more akin to a database log than a traditional messaging system. We will outline some elements of the design in the following sections.
+
+    <h3><a id="persistence" href="#persistence">4.2 Persistence</a></h3>
+    <h4><a id="design_filesystem" href="#design_filesystem">Don't fear the filesystem!</a></h4>
+    <p>
+    Kafka relies heavily on the filesystem for storing and caching messages. There is a general perception that "disks are slow" which makes people skeptical that a persistent structure can offer competitive performance.
+    In fact disks are both much slower and much faster than people expect depending on how they are used; and a properly designed disk structure can often be as fast as the network.
+    <p>
+    The key fact about disk performance is that the throughput of hard drives has been diverging from the latency of a disk seek for the last decade. As a result the performance of linear writes on a <a href="http://en.wikipedia.org/wiki/Non-RAID_drive_architectures">JBOD</a>
+    configuration with six 7200rpm SATA RAID-5 array is about 600MB/sec but the performance of random writes is only about 100k/sec&mdash;a difference of over 6000X. These linear reads and writes are the most
+    predictable of all usage patterns, and are heavily optimized by the operating system. A modern operating system provides read-ahead and write-behind techniques that prefetch data in large block multiples and
+    group smaller logical writes into large physical writes. A further discussion of this issue can be found in this <a href="http://queue.acm.org/detail.cfm?id=1563874">ACM Queue article</a>; they actually find that
+    <a href="http://deliveryimages.acm.org/10.1145/1570000/1563874/jacobs3.jpg">sequential disk access can in some cases be faster than random memory access!</a>
+    <p>
+    To compensate for this performance divergence, modern operating systems have become increasingly aggressive in their use of main memory for disk caching. A modern OS will happily divert <i>all</i> free memory to
+    disk caching with little performance penalty when the memory is reclaimed. All disk reads and writes will go through this unified cache. This feature cannot easily be turned off without using direct I/O, so even
+    if a process maintains an in-process cache of the data, this data will likely be duplicated in OS pagecache, effectively storing everything twice.
+    <p>
+    Furthermore, we are building on top of the JVM, and anyone who has spent any time with Java memory usage knows two things:
+    <ol>
+        <li>The memory overhead of objects is very high, often doubling the size of the data stored (or worse).</li>
+        <li>Java garbage collection becomes increasingly fiddly and slow as the in-heap data increases.</li>
+    </ol>
+    <p>
+    As a result of these factors using the filesystem and relying on pagecache is superior to maintaining an in-memory cache or other structure&mdash;we at least double the available cache by having automatic access
+    to all free memory, and likely double again by storing a compact byte structure rather than individual objects. Doing so will result in a cache of up to 28-30GB on a 32GB machine without GC penalties.
+    Furthermore, this cache will stay warm even if the service is restarted, whereas the in-process cache will need to be rebuilt in memory (which for a 10GB cache may take 10 minutes) or else it will need to start
+    with a completely cold cache (which likely means terrible initial performance). This also greatly simplifies the code as all logic for maintaining coherency between the cache and filesystem is now in the OS,
+    which tends to do so more efficiently and more correctly than one-off in-process attempts. If your disk usage favors linear reads then read-ahead is effectively pre-populating this cache with useful data on each
+    disk read.
+    <p>
+    This suggests a design which is very simple: rather than maintain as much as possible in-memory and flush it all out to the filesystem in a panic when we run out of space, we invert that. All data is immediately
+    written to a persistent log on the filesystem without necessarily flushing to disk. In effect this just means that it is transferred into the kernel's pagecache.
+    <p>
+    This style of pagecache-centric design is described in an <a href="http://varnish-cache.org/wiki/ArchitectNotes">article</a> on the design of Varnish here (along with a healthy dose of arrogance).
+
+    <h4><a id="design_constanttime" href="#design_constanttime">Constant Time Suffices</a></h4>
+    <p>
+    The persistent data structure used in messaging systems are often a per-consumer queue with an associated BTree or other general-purpose random access data structures to maintain metadata about messages.
+    BTrees are the most versatile data structure available, and make it possible to support a wide variety of transactional and non-transactional semantics in the messaging system.
+    They do come with a fairly high cost, though: Btree operations are O(log N). Normally O(log N) is considered essentially equivalent to constant time, but this is not true for disk operations.
+    Disk seeks come at 10 ms a pop, and each disk can do only one seek at a time so parallelism is limited. Hence even a handful of disk seeks leads to very high overhead.
+    Since storage systems mix very fast cached operations with very slow physical disk operations, the observed performance of tree structures is often superlinear as data increases with fixed cache--i.e. doubling
+    your data makes things much worse than twice as slow.
+    <p>
+    Intuitively a persistent queue could be built on simple reads and appends to files as is commonly the case with logging solutions. This structure has the advantage that all operations are O(1) and reads do not
+    block writes or each other. This has obvious performance advantages since the performance is completely decoupled from the data size&mdash;one server can now take full advantage of a number of cheap,
+    low-rotational speed 1+TB SATA drives. Though they have poor seek performance, these drives have acceptable performance for large reads and writes and come at 1/3 the price and 3x the capacity.
+    <p>
+    Having access to virtually unlimited disk space without any performance penalty means that we can provide some features not usually found in a messaging system. For example, in Kafka, instead of attempting to
+    delete messages as soon as they are consumed, we can retain messages for a relatively long period (say a week). This leads to a great deal of flexibility for consumers, as we will describe.
+
+    <h3><a id="maximizingefficiency" href="#maximizingefficiency">4.3 Efficiency</a></h3>
+    <p>
+    We have put significant effort into efficiency. One of our primary use cases is handling web activity data, which is very high volume: each page view may generate dozens of writes. Furthermore, we assume each
+    message published is read by at least one consumer (often many), hence we strive to make consumption as cheap as possible.
+    <p>
+    We have also found, from experience building and running a number of similar systems, that efficiency is a key to effective multi-tenant operations. If the downstream infrastructure service can easily become a
+    bottleneck due to a small bump in usage by the application, such small changes will often create problems. By being very fast we help ensure that the application will tip-over under load before the infrastructure.
+    This is particularly important when trying to run a centralized service that supports dozens or hundreds of applications on a centralized cluster as changes in usage patterns are a near-daily occurrence.
+    <p>
+    We discussed disk efficiency in the previous section. Once poor disk access patterns have been eliminated, there are two common causes of inefficiency in this type of system: too many small I/O operations, and
+    excessive byte copying.
+    <p>
+    The small I/O problem happens both between the client and the server and in the server's own persistent operations.
+    <p>
+    To avoid this, our protocol is built around a "message set" abstraction that naturally groups messages together. This allows network requests to group messages together and amortize the overhead of the network
+    roundtrip rather than sending a single message at a time. The server in turn appends chunks of messages to its log in one go, and the consumer fetches large linear chunks at a time.
+    <p>
+    This simple optimization produces orders of magnitude speed up. Batching leads to larger network packets, larger sequential disk operations, contiguous memory blocks, and so on, all of which allows Kafka to turn
+    a bursty stream of random message writes into linear writes that flow to the consumers.
+    <p>
+    The other inefficiency is in byte copying. At low message rates this is not an issue, but under load the impact is significant. To avoid this we employ a standardized binary message format that is shared by the
+    producer, the broker, and the consumer (so data chunks can be transferred without modification between them).
+    <p>
+    The message log maintained by the broker is itself just a directory of files, each populated by a sequence of message sets that have been written to disk in the same format used by the producer and consumer.
+    Maintaining this common format allows optimization of the most important operation: network transfer of persistent log chunks. Modern unix operating systems offer a highly optimized code path for transferring data
+    out of pagecache to a socket; in Linux this is done with the <a href="http://man7.org/linux/man-pages/man2/sendfile.2.html">sendfile system call</a>.
+    <p>
+    To understand the impact of sendfile, it is important to understand the common data path for transfer of data from file to socket:
+    <ol>
+        <li>The operating system reads data from the disk into pagecache in kernel space</li>
+        <li>The application reads the data from kernel space into a user-space buffer</li>
+        <li>The application writes the data back into kernel space into a socket buffer</li>
+        <li>The operating system copies the data from the socket buffer to the NIC buffer where it is sent over the network</li>
+    </ol>
+    <p>
+    This is clearly inefficient, there are four copies and two system calls. Using sendfile, this re-copying is avoided by allowing the OS to send the data from pagecache to the network directly. So in this optimized
+    path, only the final copy to the NIC buffer is needed.
+    <p>
+    We expect a common use case to be multiple consumers on a topic. Using the zero-copy optimization above, data is copied into pagecache exactly once and reused on each consumption instead of being stored in memory
+    and copied out to user-space every time it is read. This allows messages to be consumed at a rate that approaches the limit of the network connection.
+    <p>
+    This combination of pagecache and sendfile means that on a Kafka cluster where the consumers are mostly caught up you will see no read activity on the disks whatsoever as they will be serving data entirely from cache.
+    <p>
+    For more background on the sendfile and zero-copy support in Java, see this <a href="http://www.ibm.com/developerworks/linux/library/j-zerocopy">article</a>.
+
+    <h4><a id="design_compression" href="#design_compression">End-to-end Batch Compression</a></h4>
+    <p>
+    In some cases the bottleneck is actually not CPU or disk but network bandwidth. This is particularly true for a data pipeline that needs to send messages between data centers over a wide-area network. Of course,
+    the user can always compress its messages one at a time without any support needed from Kafka, but this can lead to very poor compression ratios as much of the redundancy is due to repetition between messages of
+    the same type (e.g. field names in JSON or user agents in web logs or common string values). Efficient compression requires compressing multiple messages together rather than compressing each message individually.
+    <p>
+    Kafka supports this with an efficient batching format. A batch of messages can be clumped together compressed and sent to the server in this form. This batch of messages will be written in compressed form and will
+    remain compressed in the log and will only be decompressed by the consumer.
+    <p>
+    Kafka supports GZIP, Snappy, LZ4 and ZStandard compression protocols. More details on compression can be found <a href="https://cwiki.apache.org/confluence/display/KAFKA/Compression">here</a>.
+
+    <h3><a id="theproducer" href="#theproducer">4.4 The Producer</a></h3>
+
+    <h4><a id="design_loadbalancing" href="#design_loadbalancing">Load balancing</a></h4>
+    <p>
+    The producer sends data directly to the broker that is the leader for the partition without any intervening routing tier. To help the producer do this all Kafka nodes can answer a request for metadata about which
+    servers are alive and where the leaders for the partitions of a topic are at any given time to allow the producer to appropriately direct its requests.
+    <p>
+    The client controls which partition it publishes messages to. This can be done at random, implementing a kind of random load balancing, or it can be done by some semantic partitioning function. We expose the interface
+    for semantic partitioning by allowing the user to specify a key to partition by and using this to hash to a partition (there is also an option to override the partition function if need be). For example if the key
+    chosen was a user id then all data for a given user would be sent to the same partition. This in turn will allow consumers to make locality assumptions about their consumption. This style of partitioning is explicitly
+    designed to allow locality-sensitive processing in consumers.
+
+    <h4><a id="design_asyncsend" href="#design_asyncsend">Asynchronous send</a></h4>
+    <p>
+    Batching is one of the big drivers of efficiency, and to enable batching the Kafka producer will attempt to accumulate data in memory and to send out larger batches in a single request. The batching can be configured
+    to accumulate no more than a fixed number of messages and to wait no longer than some fixed latency bound (say 64k or 10 ms). This allows the accumulation of more bytes to send, and few larger I/O operations on the
+    servers. This buffering is configurable and gives a mechanism to trade off a small amount of additional latency for better throughput.
+    <p>
+    Details on <a href="#producerconfigs">configuration</a> and the <a href="http://kafka.apache.org/082/javadoc/index.html?org/apache/kafka/clients/producer/KafkaProducer.html">api</a> for the producer can be found
+    elsewhere in the documentation.
+
+    <h3><a id="theconsumer" href="#theconsumer">4.5 The Consumer</a></h3>
+
+    The Kafka consumer works by issuing "fetch" requests to the brokers leading the partitions it wants to consume. The consumer specifies its offset in the log with each request and receives back a chunk of log
+    beginning from that position. The consumer thus has significant control over this position and can rewind it to re-consume data if need be.
+
+    <h4><a id="design_pull" href="#design_pull">Push vs. pull</a></h4>
+    <p>
+    An initial question we considered is whether consumers should pull data from brokers or brokers should push data to the consumer. In this respect Kafka follows a more traditional design, shared by most messaging
+    systems, where data is pushed to the broker from the producer and pulled from the broker by the consumer. Some logging-centric systems, such as <a href="http://github.com/facebook/scribe">Scribe</a> and
+    <a href="http://flume.apache.org/">Apache Flume</a>, follow a very different push-based path where data is pushed downstream. There are pros and cons to both approaches. However, a push-based system has difficulty
+    dealing with diverse consumers as the broker controls the rate at which data is transferred. The goal is generally for the consumer to be able to consume at the maximum possible rate; unfortunately, in a push
+    system this means the consumer tends to be overwhelmed when its rate of consumption falls below the rate of production (a denial of service attack, in essence). A pull-based system has the nicer property that
+    the consumer simply falls behind and catches up when it can. This can be mitigated with some kind of backoff protocol by which the consumer can indicate it is overwhelmed, but getting the rate of transfer to
+    fully utilize (but never over-utilize) the consumer is trickier than it seems. Previous attempts at building systems in this fashion led us to go with a more traditional pull model.
+    <p>
+    Another advantage of a pull-based system is that it lends itself to aggressive batching of data sent to the consumer. A push-based system must choose to either send a request immediately or accumulate more data
+    and then send it later without knowledge of whether the downstream consumer will be able to immediately process it. If tuned for low latency, this will result in sending a single message at a time only for the
+    transfer to end up being buffered anyway, which is wasteful. A pull-based design fixes this as the consumer always pulls all available messages after its current position in the log (or up to some configurable max
+    size). So one gets optimal batching without introducing unnecessary latency.
+    <p>
+    The deficiency of a naive pull-based system is that if the broker has no data the consumer may end up polling in a tight loop, effectively busy-waiting for data to arrive. To avoid this we have parameters in our
+    pull request that allow the consumer request to block in a "long poll" waiting until data arrives (and optionally waiting until a given number of bytes is available to ensure large transfer sizes).
+    <p>
+    You could imagine other possible designs which would be only pull, end-to-end. The producer would locally write to a local log, and brokers would pull from that with consumers pulling from them. A similar type of
+    "store-and-forward" producer is often proposed. This is intriguing but we felt not very suitable for our target use cases which have thousands of producers. Our experience running persistent data systems at
+    scale led us to feel that involving thousands of disks in the system across many applications would not actually make things more reliable and would be a nightmare to operate. And in practice we have found that we
+    can run a pipeline with strong SLAs at large scale without a need for producer persistence.
+
+    <h4><a id="design_consumerposition" href="#design_consumerposition">Consumer Position</a></h4>
+    Keeping track of <i>what</i> has been consumed is, surprisingly, one of the key performance points of a messaging system.
+    <p>
+    Most messaging systems keep metadata about what messages have been consumed on the broker. That is, as a message is handed out to a consumer, the broker either records that fact locally immediately or it may wait
+    for acknowledgement from the consumer. This is a fairly intuitive choice, and indeed for a single machine server it is not clear where else this state could go. Since the data structures used for storage in many
+    messaging systems scale poorly, this is also a pragmatic choice--since the broker knows what is consumed it can immediately delete it, keeping the data size small.
+    <p>
+    What is perhaps not obvious is that getting the broker and consumer to come into agreement about what has been consumed is not a trivial problem. If the broker records a message as <b>consumed</b> immediately every
+    time it is handed out over the network, then if the consumer fails to process the message (say because it crashes or the request times out or whatever) that message will be lost. To solve this problem, many messaging
+    systems add an acknowledgement feature which means that messages are only marked as <b>sent</b> not <b>consumed</b> when they are sent; the broker waits for a specific acknowledgement from the consumer to record the
+    message as <b>consumed</b>. This strategy fixes the problem of losing messages, but creates new problems. First of all, if the consumer processes the message but fails before it can send an acknowledgement then the
+    message will be consumed twice. The second problem is around performance, now the broker must keep multiple states about every single message (first to lock it so it is not given out a second time, and then to mark
+    it as permanently consumed so that it can be removed). Tricky problems must be dealt with, like what to do with messages that are sent but never acknowledged.
+    <p>
+    Kafka handles this differently. Our topic is divided into a set of totally ordered partitions, each of which is consumed by exactly one consumer within each subscribing consumer group at any given time. This means
+    that the position of a consumer in each partition is just a single integer, the offset of the next message to consume. This makes the state about what has been consumed very small, just one number for each partition.
+    This state can be periodically checkpointed. This makes the equivalent of message acknowledgements very cheap.
+    <p>
+    There is a side benefit of this decision. A consumer can deliberately <i>rewind</i> back to an old offset and re-consume data. This violates the common contract of a queue, but turns out to be an essential feature
+    for many consumers. For example, if the consumer code has a bug and is discovered after some messages are consumed, the consumer can re-consume those messages once the bug is fixed.
+
+    <h4><a id="design_offlineload" href="#design_offlineload">Offline Data Load</a></h4>
+
+    Scalable persistence allows for the possibility of consumers that only periodically consume such as batch data loads that periodically bulk-load data into an offline system such as Hadoop or a relational data
+    warehouse.
+    <p>
+    In the case of Hadoop we parallelize the data load by splitting the load over individual map tasks, one for each node/topic/partition combination, allowing full parallelism in the loading. Hadoop provides the task
+    management, and tasks which fail can restart without danger of duplicate data&mdash;they simply restart from their original position.
+
+    <h3><a id="semantics" href="#semantics">4.6 Message Delivery Semantics</a></h3>
+    <p>
+    Now that we understand a little about how producers and consumers work, let's discuss the semantic guarantees Kafka provides between producer and consumer. Clearly there are multiple possible message delivery
+    guarantees that could be provided:
+    <ul>
+    <li>
+        <i>At most once</i>&mdash;Messages may be lost but are never redelivered.
+    </li>
+    <li>
+        <i>At least once</i>&mdash;Messages are never lost but may be redelivered.
+    </li>
+    <li>
+        <i>Exactly once</i>&mdash;this is what people actually want, each message is delivered once and only once.
+    </li>
+    </ul>
+
+    It's worth noting that this breaks down into two problems: the durability guarantees for publishing a message and the guarantees when consuming a message.
+    <p>
+    Many systems claim to provide "exactly once" delivery semantics, but it is important to read the fine print, most of these claims are misleading (i.e. they don't translate to the case where consumers or producers
+    can fail, cases where there are multiple consumer processes, or cases where data written to disk can be lost).
+    <p>
+    Kafka's semantics are straight-forward. When publishing a message we have a notion of the message being "committed" to the log. Once a published message is committed it will not be lost as long as one broker that
+    replicates the partition to which this message was written remains "alive". The definition of committed message, alive partition as well as a description of which types of failures we attempt to handle will be
+    described in more detail in the next section. For now let's assume a perfect, lossless broker and try to understand the guarantees to the producer and consumer. If a producer attempts to publish a message and
+    experiences a network error it cannot be sure if this error happened before or after the message was committed. This is similar to the semantics of inserting into a database table with an autogenerated key.
+    <p>
+    Prior to 0.11.0.0, if a producer failed to receive a response indicating that a message was committed, it had little choice but to resend the message. This provides at-least-once delivery semantics since the
+    message may be written to the log again during resending if the original request had in fact succeeded. Since 0.11.0.0, the Kafka producer also supports an idempotent delivery option which guarantees that resending
+    will not result in duplicate entries in the log. To achieve this, the broker assigns each producer an ID and deduplicates messages using a sequence number that is sent by the producer along with every message.
+    Also beginning with 0.11.0.0, the producer supports the ability to send messages to multiple topic partitions using transaction-like semantics: i.e. either all messages are successfully written or none of them are.
+    The main use case for this is exactly-once processing between Kafka topics (described below).
+    <p>
+    Not all use cases require such strong guarantees. For uses which are latency sensitive we allow the producer to specify the durability level it desires. If the producer specifies that it wants to wait on the message
+    being committed this can take on the order of 10 ms. However the producer can also specify that it wants to perform the send completely asynchronously or that it wants to wait only until the leader (but not
+    necessarily the followers) have the message.
+    <p>
+    Now let's describe the semantics from the point-of-view of the consumer. All replicas have the exact same log with the same offsets. The consumer controls its position in this log. If the consumer never crashed it
+    could just store this position in memory, but if the consumer fails and we want this topic partition to be taken over by another process the new process will need to choose an appropriate position from which to start
+    processing. Let's say the consumer reads some messages -- it has several options for processing the messages and updating its position.
+    <ol>
+    <li>It can read the messages, then save its position in the log, and finally process the messages. In this case there is a possibility that the consumer process crashes after saving its position but before saving
+    the output of its message processing. In this case the process that took over processing would start at the saved position even though a few messages prior to that position had not been processed. This corresponds
+    to "at-most-once" semantics as in the case of a consumer failure messages may not be processed.
+    <li>It can read the messages, process the messages, and finally save its position. In this case there is a possibility that the consumer process crashes after processing messages but before saving its position.
+    In this case when the new process takes over the first few messages it receives will already have been processed. This corresponds to the "at-least-once" semantics in the case of consumer failure. In many cases
+    messages have a primary key and so the updates are idempotent (receiving the same message twice just overwrites a record with another copy of itself).
+    </ol>
+    <p>
+    So what about exactly once semantics (i.e. the thing you actually want)? When consuming from a Kafka topic and producing to another topic (as in a <a href="https://kafka.apache.org/documentation/streams">Kafka Streams</a>
+    application), we can leverage the new transactional producer capabilities in 0.11.0.0 that were mentioned above. The consumer's position is stored as a message in a topic, so we can write the offset to Kafka in the
+    same transaction as the output topics receiving the processed data. If the transaction is aborted, the consumer's position will revert to its old value and the produced data on the output topics will not be visible
+    to other consumers, depending on their "isolation level." In the default "read_uncommitted" isolation level, all messages are visible to consumers even if they were part of an aborted transaction,
+    but in "read_committed," the consumer will only return messages from transactions which were committed (and any messages which were not part of a transaction).
+    <p>
+    When writing to an external system, the limitation is in the need to coordinate the consumer's position with what is actually stored as output. The classic way of achieving this would be to introduce a two-phase
+    commit between the storage of the consumer position and the storage of the consumers output. But this can be handled more simply and generally by letting the consumer store its offset in the same place as
+    its output. This is better because many of the output systems a consumer might want to write to will not support a two-phase commit. As an example of this, consider a
+    <a href="https://kafka.apache.org/documentation/#connect">Kafka Connect</a> connector which populates data in HDFS along with the offsets of the data it reads so that it is guaranteed that either data and
+    offsets are both updated or neither is. We follow similar patterns for many other data systems which require these stronger semantics and for which the messages do not have a primary key to allow for deduplication.
+    <p>
+    So effectively Kafka supports exactly-once delivery in <a href="https://kafka.apache.org/documentation/streams">Kafka Streams</a>, and the transactional producer/consumer can be used generally to provide
+    exactly-once delivery when transfering and processing data between Kafka topics. Exactly-once delivery for other destination systems generally requires cooperation with such systems, but Kafka provides the
+    offset which makes implementing this feasible (see also <a href="https://kafka.apache.org/documentation/#connect">Kafka Connect</a>). Otherwise, Kafka guarantees at-least-once delivery by default, and allows
+    the user to implement at-most-once delivery by disabling retries on the producer and committing offsets in the consumer prior to processing a batch of messages.
+
+    <h3><a id="replication" href="#replication">4.7 Replication</a></h3>
+    <p>
+    Kafka replicates the log for each topic's partitions across a configurable number of servers (you can set this replication factor on a topic-by-topic basis). This allows automatic failover to these replicas when a
+    server in the cluster fails so messages remain available in the presence of failures.
+    <p>
+    Other messaging systems provide some replication-related features, but, in our (totally biased) opinion, this appears to be a tacked-on thing, not heavily used, and with large downsides: slaves are inactive,
+    throughput is heavily impacted, it requires fiddly manual configuration, etc. Kafka is meant to be used with replication by default&mdash;in fact we implement un-replicated topics as replicated topics where the
+    replication factor is one.
+    <p>
+    The unit of replication is the topic partition. Under non-failure conditions, each partition in Kafka has a single leader and zero or more followers. The total number of replicas including the leader constitute the
+    replication factor. All reads and writes go to the leader of the partition. Typically, there are many more partitions than brokers and the leaders are evenly distributed among brokers. The logs on the followers are
+    identical to the leader's log&mdash;all have the same offsets and messages in the same order (though, of course, at any given time the leader may have a few as-yet unreplicated messages at the end of its log).
+    <p>
+    Followers consume messages from the leader just as a normal Kafka consumer would and apply them to their own log. Having the followers pull from the leader has the nice property of allowing the follower to naturally
+    batch together log entries they are applying to their log.
+    <p>
+    As with most distributed systems automatically handling failures requires having a precise definition of what it means for a node to be "alive". For Kafka node liveness has two conditions
+    <ol>
+        <li>A node must be able to maintain its session with ZooKeeper (via ZooKeeper's heartbeat mechanism)
+        <li>If it is a slave it must replicate the writes happening on the leader and not fall "too far" behind
+    </ol>
+    We refer to nodes satisfying these two conditions as being "in sync" to avoid the vagueness of "alive" or "failed". The leader keeps track of the set of "in sync" nodes. If a follower dies, gets stuck, or falls
+    behind, the leader will remove it from the list of in sync replicas. The determination of stuck and lagging replicas is controlled by the replica.lag.time.max.ms configuration.
+    <p>
+    In distributed systems terminology we only attempt to handle a "fail/recover" model of failures where nodes suddenly cease working and then later recover (perhaps without knowing that they have died). Kafka does not
+    handle so-called "Byzantine" failures in which nodes produce arbitrary or malicious responses (perhaps due to bugs or foul play).
+    <p>
+    We can now more precisely define that a message is considered committed when all in sync replicas for that partition have applied it to their log.
+    Only committed messages are ever given out to the consumer. This means that the consumer need not worry about potentially seeing a message that could be lost if the leader fails. Producers, on the other hand,
+    have the option of either waiting for the message to be committed or not, depending on their preference for tradeoff between latency and durability. This preference is controlled by the acks setting that the
+    producer uses.
+    Note that topics have a setting for the "minimum number" of in-sync replicas that is checked when the producer requests acknowledgment that a message
+    has been written to the full set of in-sync replicas. If a less stringent acknowledgement is requested by the producer, then the message can be committed, and consumed,
+    even if the number of in-sync replicas is lower than the minimum (e.g. it can be as low as just the leader).
+    <p>
+    The guarantee that Kafka offers is that a committed message will not be lost, as long as there is at least one in sync replica alive, at all times.
+    <p>
+    Kafka will remain available in the presence of node failures after a short fail-over period, but may not remain available in the presence of network partitions.
+
+    <h4><a id="design_replicatedlog" href="#design_replicatedlog">Replicated Logs: Quorums, ISRs, and State Machines (Oh my!)</a></h4>
+
+    At its heart a Kafka partition is a replicated log. The replicated log is one of the most basic primitives in distributed data systems, and there are many approaches for implementing one. A replicated log can be
+    used by other systems as a primitive for implementing other distributed systems in the <a href="http://en.wikipedia.org/wiki/State_machine_replication">state-machine style</a>.
+    <p>
+    A replicated log models the process of coming into consensus on the order of a series of values (generally numbering the log entries 0, 1, 2, ...). There are many ways to implement this, but the simplest and fastest
+    is with a leader who chooses the ordering of values provided to it. As long as the leader remains alive, all followers need to only copy the values and ordering the leader chooses.
+    <p>
+    Of course if leaders didn't fail we wouldn't need followers! When the leader does die we need to choose a new leader from among the followers. But followers themselves may fall behind or crash so we must ensure we
+    choose an up-to-date follower. The fundamental guarantee a log replication algorithm must provide is that if we tell the client a message is committed, and the leader fails, the new leader we elect must also have
+    that message. This yields a tradeoff: if the leader waits for more followers to acknowledge a message before declaring it committed then there will be more potentially electable leaders.
+    <p>
+    If you choose the number of acknowledgements required and the number of logs that must be compared to elect a leader such that there is guaranteed to be an overlap, then this is called a Quorum.
+    <p>
+    A common approach to this tradeoff is to use a majority vote for both the commit decision and the leader election. This is not what Kafka does, but let's explore it anyway to understand the tradeoffs. Let's say we
+    have 2<i>f</i>+1 replicas. If <i>f</i>+1 replicas must receive a message prior to a commit being declared by the leader, and if we elect a new leader by electing the follower with the most complete log from at least
+    <i>f</i>+1 replicas, then, with no more than <i>f</i> failures, the leader is guaranteed to have all committed messages. This is because among any <i>f</i>+1 replicas, there must be at least one replica that contains
+    all committed messages. That replica's log will be the most complete and therefore will be selected as the new leader. There are many remaining details that each algorithm must handle (such as precisely defined what
+    makes a log more complete, ensuring log consistency during leader failure or changing the set of servers in the replica set) but we will ignore these for now.
+    <p>
+    This majority vote approach has a very nice property: the latency is dependent on only the fastest servers. That is, if the replication factor is three, the latency is determined by the faster slave not the slower one.
+    <p>
+    There are a rich variety of algorithms in this family including ZooKeeper's
+    <a href="http://web.archive.org/web/20140602093727/http://www.stanford.edu/class/cs347/reading/zab.pdf">Zab</a>,
+    <a href="https://ramcloud.stanford.edu/wiki/download/attachments/11370504/raft.pdf">Raft</a>,
+    and <a href="http://pmg.csail.mit.edu/papers/vr-revisited.pdf">Viewstamped Replication</a>.
+    The most similar academic publication we are aware of to Kafka's actual implementation is
+    <a href="http://research.microsoft.com/apps/pubs/default.aspx?id=66814">PacificA</a> from Microsoft.
+    <p>
+    The downside of majority vote is that it doesn't take many failures to leave you with no electable leaders. To tolerate one failure requires three copies of the data, and to tolerate two failures requires five copies
+    of the data. In our experience having only enough redundancy to tolerate a single failure is not enough for a practical system, but doing every write five times, with 5x the disk space requirements and 1/5th the
+    throughput, is not very practical for large volume data problems. This is likely why quorum algorithms more commonly appear for shared cluster configuration such as ZooKeeper but are less common for primary data
+    storage. For example in HDFS the namenode's high-availability feature is built on a <a href="http://blog.cloudera.com/blog/2012/10/quorum-based-journaling-in-cdh4-1">majority-vote-based journal</a>, but this more
+    expensive approach is not used for the data itself.
+    <p>
+    Kafka takes a slightly different approach to choosing its quorum set. Instead of majority vote, Kafka dynamically maintains a set of in-sync replicas (ISR) that are caught-up to the leader. Only members of this set
+    are eligible for election as leader. A write to a Kafka partition is not considered committed until <i>all</i> in-sync replicas have received the write. This ISR set is persisted to ZooKeeper whenever it changes.
+    Because of this, any replica in the ISR is eligible to be elected leader. This is an important factor for Kafka's usage model where there are many partitions and ensuring leadership balance is important.
+    With this ISR model and <i>f+1</i> replicas, a Kafka topic can tolerate <i>f</i> failures without losing committed messages.
+    <p>
+    For most use cases we hope to handle, we think this tradeoff is a reasonable one. In practice, to tolerate <i>f</i> failures, both the majority vote and the ISR approach will wait for the same number of replicas to
+    acknowledge before committing a message (e.g. to survive one failure a majority quorum needs three replicas and one acknowledgement and the ISR approach requires two replicas and one acknowledgement).
+    The ability to commit without the slowest servers is an advantage of the majority vote approach. However, we think it is ameliorated by allowing the client to choose whether they block on the message commit or not,
+    and the additional throughput and disk space due to the lower required replication factor is worth it.
+    <p>
+    Another important design distinction is that Kafka does not require that crashed nodes recover with all their data intact. It is not uncommon for replication algorithms in this space to depend on the existence of
+    "stable storage" that cannot be lost in any failure-recovery scenario without potential consistency violations. There are two primary problems with this assumption. First, disk errors are the most common problem we
+    observe in real operation of persistent data systems and they often do not leave data intact. Secondly, even if this were not a problem, we do not want to require the use of fsync on every write for our consistency
+    guarantees as this can reduce performance by two to three orders of magnitude. Our protocol for allowing a replica to rejoin the ISR ensures that before rejoining, it must fully re-sync again even if it lost unflushed
+    data in its crash.
+
+    <h4><a id="design_uncleanleader" href="#design_uncleanleader">Unclean leader election: What if they all die?</a></h4>
+
+    Note that Kafka's guarantee with respect to data loss is predicated on at least one replica remaining in sync. If all the nodes replicating a partition die, this guarantee no longer holds.
+    <p>
+    However a practical system needs to do something reasonable when all the replicas die. If you are unlucky enough to have this occur, it is important to consider what will happen. There are two behaviors that could be
+    implemented:
+    <ol>
+        <li>Wait for a replica in the ISR to come back to life and choose this replica as the leader (hopefully it still has all its data).
+        <li>Choose the first replica (not necessarily in the ISR) that comes back to life as the leader.
+    </ol>
+    <p>
+    This is a simple tradeoff between availability and consistency. If we wait for replicas in the ISR, then we will remain unavailable as long as those replicas are down. If such replicas were destroyed or their data
+    was lost, then we are permanently down. If, on the other hand, a non-in-sync replica comes back to life and we allow it to become leader, then its log becomes the source of truth even though it is not guaranteed to
+    have every committed message. By default from version 0.11.0.0, Kafka chooses the first strategy and favor waiting for a consistent replica. This behavior can be changed using
+    configuration property unclean.leader.election.enable, to support use cases where uptime is preferable to consistency.
+    <p>
+    This dilemma is not specific to Kafka. It exists in any quorum-based scheme. For example in a majority voting scheme, if a majority of servers suffer a permanent failure, then you must either choose to lose 100% of
+    your data or violate consistency by taking what remains on an existing server as your new source of truth.
+
+
+    <h4><a id="design_ha" href="#design_ha">Availability and Durability Guarantees</a></h4>
+
+    When writing to Kafka, producers can choose whether they wait for the message to be acknowledged by 0,1 or all (-1) replicas.
+    Note that "acknowledgement by all replicas" does not guarantee that the full set of assigned replicas have received the message. By default, when acks=all, acknowledgement happens as soon as all the current in-sync
+    replicas have received the message. For example, if a topic is configured with only two replicas and one fails (i.e., only one in sync replica remains), then writes that specify acks=all will succeed. However, these
+    writes could be lost if the remaining replica also fails.
+
+    Although this ensures maximum availability of the partition, this behavior may be undesirable to some users who prefer durability over availability. Therefore, we provide two topic-level configurations that can be
+    used to prefer message durability over availability:
+    <ol>
+        <li> Disable unclean leader election - if all replicas become unavailable, then the partition will remain unavailable until the most recent leader becomes available again. This effectively prefers unavailability
+        over the risk of message loss. See the previous section on Unclean Leader Election for clarification. </li>
+        <li> Specify a minimum ISR size - the partition will only accept writes if the size of the ISR is above a certain minimum, in order to prevent the loss of messages that were written to just a single replica,
+        which subsequently becomes unavailable. This setting only takes effect if the producer uses acks=all and guarantees that the message will be acknowledged by at least this many in-sync replicas.
+    This setting offers a trade-off between consistency and availability. A higher setting for minimum ISR size guarantees better consistency since the message is guaranteed to be written to more replicas which reduces
+    the probability that it will be lost. However, it reduces availability since the partition will be unavailable for writes if the number of in-sync replicas drops below the minimum threshold. </li>
+    </ol>
+
+
+    <h4><a id="design_replicamanagment" href="#design_replicamanagment">Replica Management</a></h4>
+
+    The above discussion on replicated logs really covers only a single log, i.e. one topic partition. However a Kafka cluster will manage hundreds or thousands of these partitions. We attempt to balance partitions
+    within a cluster in a round-robin fashion to avoid clustering all partitions for high-volume topics on a small number of nodes. Likewise we try to balance leadership so that each node is the leader for a proportional
+    share of its partitions.
+    <p>
+    It is also important to optimize the leadership election process as that is the critical window of unavailability. A naive implementation of leader election would end up running an election per partition for all
+    partitions a node hosted when that node failed. Instead, we elect one of the brokers as the "controller". This controller detects failures at the broker level and is responsible for changing the leader of all
+    affected partitions in a failed broker. The result is that we are able to batch together many of the required leadership change notifications which makes the election process far cheaper and faster for a large number
+    of partitions. If the controller fails, one of the surviving brokers will become the new controller.
+
+    <h3><a id="compaction" href="#compaction">4.8 Log Compaction</a></h3>
+
+    Log compaction ensures that Kafka will always retain at least the last known value for each message key within the log of data for a single topic partition.  It addresses use cases and scenarios such as restoring
+    state after application crashes or system failure, or reloading caches after application restarts during operational maintenance. Let's dive into these use cases in more detail and then describe how compaction works.
+    <p>
+    So far we have described only the simpler approach to data retention where old log data is discarded after a fixed period of time or when the log reaches some predetermined size. This works well for temporal event
+    data such as logging where each record stands alone. However an important class of data streams are the log of changes to keyed, mutable data (for example, the changes to a database table).
+    <p>
+    Let's discuss a concrete example of such a stream. Say we have a topic containing user email addresses; every time a user updates their email address we send a message to this topic using their user id as the
+    primary key. Now say we send the following messages over some time period for a user with id 123, each message corresponding to a change in email address (messages for other ids are omitted):
+    <pre class="brush: text;">
+        123 => bill@microsoft.com
+                .
+                .
+                .
+        123 => bill@gatesfoundation.org
+                .
+                .
+                .
+        123 => bill@gmail.com
+    </pre>
+    Log compaction gives us a more granular retention mechanism so that we are guaranteed to retain at least the last update for each primary key (e.g. <code>bill@gmail.com</code>). By doing this we guarantee that the
+    log contains a full snapshot of the final value for every key not just keys that changed recently. This means downstream consumers can restore their own state off this topic without us having to retain a complete
+    log of all changes.
+    <p>
+    Let's start by looking at a few use cases where this is useful, then we'll see how it can be used.
+    <ol>
+    <li><i>Database change subscription</i>. It is often necessary to have a data set in multiple data systems, and often one of these systems is a database of some kind (either a RDBMS or perhaps a new-fangled key-value
+    store). For example you might have a database, a cache, a search cluster, and a Hadoop cluster. Each change to the database will need to be reflected in the cache, the search cluster, and eventually in Hadoop.
+    In the case that one is only handling the real-time updates you only need recent log. But if you want to be able to reload the cache or restore a failed search node you may need a complete data set.
+    <li><i>Event sourcing</i>. This is a style of application design which co-locates query processing with application design and uses a log of changes as the primary store for the application.
+    <li><i>Journaling for high-availability</i>. A process that does local computation can be made fault-tolerant by logging out changes that it makes to its local state so another process can reload these changes and
+    carry on if it should fail. A concrete example of this is handling counts, aggregations, and other "group by"-like processing in a stream query system. Samza, a real-time stream-processing framework,
+    <a href="http://samza.apache.org/learn/documentation/0.7.0/container/state-management.html">uses this feature</a> for exactly this purpose.
+    </ol>
+    In each of these cases one needs primarily to handle the real-time feed of changes, but occasionally, when a machine crashes or data needs to be re-loaded or re-processed, one needs to do a full load.
+    Log compaction allows feeding both of these use cases off the same backing topic.
+
+    This style of usage of a log is described in more detail in <a href="http://engineering.linkedin.com/distributed-systems/log-what-every-software-engineer-should-know-about-real-time-datas-unifying">this blog post</a>.
+    <p>
+    The general idea is quite simple. If we had infinite log retention, and we logged each change in the above cases, then we would have captured the state of the system at each time from when it first began.
+    Using this complete log, we could restore to any point in time by replaying the first N records in the log. This hypothetical complete log is not very practical for systems that update a single record many times
+    as the log will grow without bound even for a stable dataset. The simple log retention mechanism which throws away old updates will bound space but the log is no longer a way to restore the current state&mdash;now
+    restoring from the beginning of the log no longer recreates the current state as old updates may not be captured at all.
+    <p>
+    Log compaction is a mechanism to give finer-grained per-record retention, rather than the coarser-grained time-based retention. The idea is to selectively remove records where we have a more recent update with the
+    same primary key. This way the log is guaranteed to have at least the last state for each key.
+    <p>
+    This retention policy can be set per-topic, so a single cluster can have some topics where retention is enforced by size or time and other topics where retention is enforced by compaction.
+    <p>
+    This functionality is inspired by one of LinkedIn's oldest and most successful pieces of infrastructure&mdash;a database changelog caching service called <a href="https://github.com/linkedin/databus">Databus</a>.
+    Unlike most log-structured storage systems Kafka is built for subscription and organizes data for fast linear reads and writes. Unlike Databus, Kafka acts as a source-of-truth store so it is useful even in
+    situations where the upstream data source would not otherwise be replayable.
+
+    <h4><a id="design_compactionbasics" href="#design_compactionbasics">Log Compaction Basics</a></h4>
+
+    Here is a high-level picture that shows the logical structure of a Kafka log with the offset for each message.
+    <p>
+    <img class="centered" src="/{{version}}/images/log_cleaner_anatomy.png">
+    <p>
+    The head of the log is identical to a traditional Kafka log. It has dense, sequential offsets and retains all messages. Log compaction adds an option for handling the tail of the log. The picture above shows a log
+    with a compacted tail. Note that the messages in the tail of the log retain the original offset assigned when they were first written&mdash;that never changes. Note also that all offsets remain valid positions in
+    the log, even if the message with that offset has been compacted away; in this case this position is indistinguishable from the next highest offset that does appear in the log. For example, in the picture above the
+    offsets 36, 37, and 38 are all equivalent positions and a read beginning at any of these offsets would return a message set beginning with 38.
+    <p>
+    Compaction also allows for deletes. A message with a key and a null payload will be treated as a delete from the log. This delete marker will cause any prior message with that key to be removed (as would any new
+    message with that key), but delete markers are special in that they will themselves be cleaned out of the log after a period of time to free up space. The point in time at which deletes are no longer retained is
+    marked as the "delete retention point" in the above diagram.
+    <p>
+    The compaction is done in the background by periodically recopying log segments. Cleaning does not block reads and can be throttled to use no more than a configurable amount of I/O throughput to avoid impacting
+    producers and consumers. The actual process of compacting a log segment looks something like this:
+    <p>
+    <img class="centered" src="/{{version}}/images/log_compaction.png">
+    <p>
+    <h4><a id="design_compactionguarantees" href="#design_compactionguarantees">What guarantees does log compaction provide?</a></h4>
+
+    Log compaction guarantees the following:
+    <ol>
+    <li>Any consumer that stays caught-up to within the head of the log will see every message that is written; these messages will have sequential offsets. The topic's <code>min.compaction.lag.ms</code> can be used to
+    guarantee the minimum length of time must pass after a message is written before it could be compacted. I.e. it provides a lower bound on how long each message will remain in the (uncompacted) head.
+    <li>Ordering of messages is always maintained.  Compaction will never re-order messages, just remove some.
+    <li>The offset for a message never changes.  It is the permanent identifier for a position in the log.
+    <li>Any consumer progressing from the start of the log will see at least the final state of all records in the order they were written.  Additionally, all delete markers for deleted records will be seen, provided
+    the consumer reaches the head of the log in a time period less than the topic's <code>delete.retention.ms</code> setting (the default is 24 hours).  In other words: since the removal of delete markers happens
+    concurrently with reads, it is possible for a consumer to miss delete markers if it lags by more than <code>delete.retention.ms</code>.
+    </ol>
+
+    <h4><a id="design_compactiondetails" href="#design_compactiondetails">Log Compaction Details</a></h4>
+
+    Log compaction is handled by the log cleaner, a pool of background threads that recopy log segment files, removing records whose key appears in the head of the log. Each compactor thread works as follows:
+    <ol>
+    <li>It chooses the log that has the highest ratio of log head to log tail
+    <li>It creates a succinct summary of the last offset for each key in the head of the log
+    <li>It recopies the log from beginning to end removing keys which have a later occurrence in the log. New, clean segments are swapped into the log immediately so the additional disk space required is just one
+    additional log segment (not a fully copy of the log).
+    <li>The summary of the log head is essentially just a space-compact hash table. It uses exactly 24 bytes per entry. As a result with 8GB of cleaner buffer one cleaner iteration can clean around 366GB of log head
+    (assuming 1k messages).
+    </ol>
+    <p>
+    <h4><a id="design_compactionconfig" href="#design_compactionconfig">Configuring The Log Cleaner</a></h4>
+
+    The log cleaner is enabled by default. This will start the pool of cleaner threads.
+    To enable log cleaning on a particular topic you can add the log-specific property
+    <pre class="brush: text;">  log.cleanup.policy=compact</pre>
+    This can be done either at topic creation time or using the alter topic command.
+    <p>
+    The log cleaner can be configured to retain a minimum amount of the uncompacted "head" of the log. This is enabled by setting the compaction time lag.
+    <pre class="brush: text;">  log.cleaner.min.compaction.lag.ms</pre>
+
+    This can be used to prevent messages newer than a minimum message age from being subject to compaction. If not set, all log segments are eligible for compaction except for the last segment, i.e. the one currently
+    being written to. The active segment will not be compacted even if all of its messages are older than the minimum compaction time lag.
+    </p>
+    <p>
+    Further cleaner configurations are described <a href="/documentation.html#brokerconfigs">here</a>.
+
+    <h3><a id="design_quotas" href="#design_quotas">4.9 Quotas</a></h3>
+    <p>
+    Kafka cluster has the ability to enforce quotas on requests to control the broker resources used by clients. Two types
+    of client quotas can be enforced by Kafka brokers for each group of clients sharing a quota:
+    <ol>
+      <li>Network bandwidth quotas define byte-rate thresholds (since 0.9)</li>
+      <li>Request rate quotas define CPU utilization thresholds as a percentage of network and I/O threads (since 0.11)</li>
+    </ol>
+    </p>
+
+    <h4><a id="design_quotasnecessary" href="#design_quotasnecessary">Why are quotas necessary?</a></h4>
+    <p>
+    It is possible for producers and consumers to produce/consume very high volumes of data or generate requests at a very high
+    rate and thus monopolize broker resources, cause network saturation and generally DOS other clients and the brokers themselves.
+    Having quotas protects against these issues and is all the more important in large multi-tenant clusters where a small set of badly behaved clients can degrade user experience for the well behaved ones.
+    In fact, when running Kafka as a service this even makes it possible to enforce API limits according to an agreed upon contract.
+    </p>
+    <h4><a id="design_quotasgroups" href="#design_quotasgroups">Client groups</a></h4>
+        The identity of Kafka clients is the user principal which represents an authenticated user in a secure cluster. In a cluster that supports unauthenticated clients, user principal is a grouping of unauthenticated
+        users
+        chosen by the broker using a configurable <code>PrincipalBuilder</code>. Client-id is a logical grouping of clients with a meaningful name chosen by the client application. The tuple (user, client-id) defines
+        a secure logical group of clients that share both user principal and client-id.
+    <p>
+        Quotas can be applied to (user, client-id), user or client-id groups. For a given connection, the most specific quota matching the connection is applied. All connections of a quota group share the quota configured for the group.
+        For example, if (user="test-user", client-id="test-client") has a produce quota of 10MB/sec, this is shared across all producer instances of user "test-user" with the client-id "test-client".
+    </p>
+    <h4><a id="design_quotasconfig" href="#design_quotasconfig">Quota Configuration</a></h4>
+    <p>
+        Quota configuration may be defined for (user, client-id), user and client-id groups. It is possible to override the default quota at any of the quota levels that needs a higher (or even lower) quota.
+        The mechanism is similar to the per-topic log config overrides.
+        User and (user, client-id) quota overrides are written to ZooKeeper under <i><b>/config/users</b></i> and client-id quota overrides are written under <i><b>/config/clients</b></i>.
+        These overrides are read by all brokers and are effective immediately. This lets us change quotas without having to do a rolling restart of the entire cluster. See <a href="#quotas">here</a> for details.
+        Default quotas for each group may also be updated dynamically using the same mechanism.
+    </p>
+    <p>
+        The order of precedence for quota configuration is:
+        <ol>
+            <li>/config/users/&lt;user&gt;/clients/&lt;client-id&gt;</li>
+            <li>/config/users/&lt;user&gt;/clients/&lt;default&gt;</li>
+            <li>/config/users/&lt;user&gt;</li>
+            <li>/config/users/&lt;default&gt;/clients/&lt;client-id&gt;</li>
+            <li>/config/users/&lt;default&gt;/clients/&lt;default&gt;</li>
+            <li>/config/users/&lt;default&gt;</li>
+            <li>/config/clients/&lt;client-id&gt;</li>
+            <li>/config/clients/&lt;default&gt;</li>
+        </ol>
+
+        Broker properties (quota.producer.default, quota.consumer.default) can also be used to set defaults of network bandwidth quotas for client-id groups. These properties are being deprecated and will be removed in a later release.
+        Default quotas for client-id can be set in Zookeeper similar to the other quota overrides and defaults.
+    </p>
+    <h4><a id="design_quotasbandwidth" href="#design_quotasbandwidth">Network Bandwidth Quotas</a></h4>
+    <p>
+        Network bandwidth quotas are defined as the byte rate threshold for each group of clients sharing a quota.
+        By default, each unique client group receives a fixed quota in bytes/sec as configured by the cluster.
+        This quota is defined on a per-broker basis. Each group of clients can publish/fetch a maximum of X bytes/sec
+        per broker before clients are throttled.
+    </p>
+    <h4><a id="design_quotascpu" href="#design_quotascpu">Request Rate Quotas</a></h4>
+    <p>
+        Request rate quotas are defined as the percentage of time a client can utilize on request handler I/O
+        threads and network threads of each broker within a quota window. A quota of <tt>n%</tt> represents
+        <tt>n%</tt> of one thread, so the quota is out of a total capacity of <tt>((num.io.threads + num.network.threads) * 100)%</tt>.
+        Each group of clients may use a total percentage of upto <tt>n%</tt> across all I/O and network threads in a quota
+        window before being throttled. Since the number of threads allocated for I/O and network threads are typically based
+        on the number of cores available on the broker host, request rate quotas represent the total percentage of CPU
+        that may be used by each group of clients sharing the quota.
+    </p>
+    <h4><a id="design_quotasenforcement" href="#design_quotasenforcement">Enforcement</a></h4>
+    <p>
+        By default, each unique client group receives a fixed quota as configured by the cluster.
+        This quota is defined on a per-broker basis. Each client can utilize this quota per broker before it gets throttled. We decided that defining these quotas per broker is much better than
+        having a fixed cluster wide bandwidth per client because that would require a mechanism to share client quota usage among all the brokers. This can be harder to get right than the quota implementation itself!
+    </p>
+    <p>
+        How does a broker react when it detects a quota violation? In our solution, the broker first computes the amount of delay needed to bring the violating client under its quota
+        and returns a response with the delay immediately. In case of a fetch request, the response will not contain any data. Then, the broker mutes the channel to the client,
+        not to process requests from the client anymore, until the delay is over. Upon receiving a response with a non-zero delay duration, the Kafka client will also refrain from
+        sending further requests to the broker during the delay. Therefore, requests from a throttled client are effectively blocked from both sides.
+        Even with older client implementations that do not respect the delay response from the broker, the back pressure applied by the broker via muting its socket channel
+        can still handle the throttling of badly behaving clients. Those clients who sent further requests to the throttled channel will receive responses only after the delay is over.
+    </p>
+    <p>
+    Byte-rate and thread utilization are measured over multiple small windows (e.g. 30 windows of 1 second each) in order to detect and correct quota violations quickly. Typically, having large measurement windows
+    (for e.g. 10 windows of 30 seconds each) leads to large bursts of traffic followed by long delays which is not great in terms of user experience.
+    </p>
+</script>
+
+<div class="p-design"></div>
diff --git a/21/documentation.html b/21/documentation.html
new file mode 100644
index 0000000..5dcf5c1
--- /dev/null
+++ b/21/documentation.html
@@ -0,0 +1,83 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<script><!--#include virtual="js/templateData.js" --></script>
+
+<!--#include virtual="../includes/_header.htm" -->
+<!--#include virtual="../includes/_top.htm" -->
+
+
+<div class="content documentation documentation--current">
+	<!--#include virtual="../includes/_nav.htm" -->
+	<div class="right">
+		<!--#include virtual="../includes/_docs_banner.htm" -->
+    <h1>Documentation</h1>
+    <h3>Kafka 2.0 Documentation</h3>
+    Prior releases: <a href="/07/documentation.html">0.7.x</a>, <a href="/08/documentation.html">0.8.0</a>, <a href="/081/documentation.html">0.8.1.X</a>, <a href="/082/documentation.html">0.8.2.X</a>, <a href="/090/documentation.html">0.9.0.X</a>, <a href="/0100/documentation.html">0.10.0.X</a>, <a href="/0101/documentation.html">0.10.1.X</a>, <a href="/0102/documentation.html">0.10.2.X</a>, <a href="/0110/documentation.html">0.11.0.X</a>, <a href="/10/documentation.html">1.0.X</a>, <a  [...]
+
+    <!--#include virtual="toc.html" -->
+
+    <h2><a id="gettingStarted" href="#gettingStarted">1. Getting Started</a></h2>
+      <h3><a id="introduction" href="#introduction">1.1 Introduction</a></h3>
+      <!--#include virtual="introduction.html" -->
+      <h3><a id="uses" href="#uses">1.2 Use Cases</a></h3>
+      <!--#include virtual="uses.html" -->
+      <h3><a id="quickstart" href="#quickstart">1.3 Quick Start</a></h3>
+      <!--#include virtual="quickstart.html" -->
+      <h3><a id="ecosystem" href="#ecosystem">1.4 Ecosystem</a></h3>
+      <!--#include virtual="ecosystem.html" -->
+      <h3><a id="upgrade" href="#upgrade">1.5 Upgrading From Previous Versions</a></h3>
+      <!--#include virtual="upgrade.html" -->
+
+    <h2><a id="api" href="#api">2. APIs</a></h2>
+
+    <!--#include virtual="api.html" -->
+
+    <h2><a id="configuration" href="#configuration">3. Configuration</a></h2>
+
+    <!--#include virtual="configuration.html" -->
+
+    <h2><a id="design" href="#design">4. Design</a></h2>
+
+    <!--#include virtual="design.html" -->
+
+    <h2><a id="implementation" href="#implementation">5. Implementation</a></h2>
+
+    <!--#include virtual="implementation.html" -->
+
+    <h2><a id="operations" href="#operations">6. Operations</a></h2>
+
+    <!--#include virtual="ops.html" -->
+
+    <h2><a id="security" href="#security">7. Security</a></h2>
+    <!--#include virtual="security.html" -->
+
+    <h2><a id="connect" href="#connect">8. Kafka Connect</a></h2>
+    <!--#include virtual="connect.html" -->
+
+    <h2><a id="streams" href="/documentation/streams">9. Kafka Streams</a></h2>
+    <p>
+        Kafka Streams is a client library for processing and analyzing data stored in Kafka. It builds upon important stream processing concepts such as properly distinguishing between event time and processing time, windowing support, exactly-once processing semantics and simple yet efficient management of application state.
+    </p>
+    <p>
+        Kafka Streams has a <b>low barrier to entry</b>: You can quickly write and run a small-scale proof-of-concept on a single machine; and you only need to run additional instances of your application on multiple machines to scale up to high-volume production workloads. Kafka Streams transparently handles the load balancing of multiple instances of the same application by leveraging Kafka's parallelism model.
+    </p>
+
+    <p>Learn More about Kafka Streams read <a href="/documentation/streams">this</a> Section.</p>
+
+<!--#include virtual="../includes/_footer.htm" -->
+<!--#include virtual="../includes/_docs_footer.htm" -->
diff --git a/21/documentation/index.html b/21/documentation/index.html
new file mode 100644
index 0000000..1d7507f
--- /dev/null
+++ b/21/documentation/index.html
@@ -0,0 +1,18 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--#include virtual="../documentation.html" -->
\ No newline at end of file
diff --git a/21/documentation/streams/architecture.html b/21/documentation/streams/architecture.html
new file mode 100644
index 0000000..ad7b323
--- /dev/null
+++ b/21/documentation/streams/architecture.html
@@ -0,0 +1,19 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- should always link the latest release's documentation -->
+<!--#include virtual="../../streams/architecture.html" -->
diff --git a/21/documentation/streams/core-concepts.html b/21/documentation/streams/core-concepts.html
new file mode 100644
index 0000000..d699b79
--- /dev/null
+++ b/21/documentation/streams/core-concepts.html
@@ -0,0 +1,19 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- should always link the latest release's documentation -->
+<!--#include virtual="../../streams/core-concepts.html" -->
diff --git a/21/documentation/streams/developer-guide/app-reset-tool.html b/21/documentation/streams/developer-guide/app-reset-tool.html
new file mode 100644
index 0000000..64a43aa
--- /dev/null
+++ b/21/documentation/streams/developer-guide/app-reset-tool.html
@@ -0,0 +1,19 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- should always link the latest release's documentation -->
+<!--#include virtual="../../../streams/developer-guide/app-reset-tool.html" -->
diff --git a/21/documentation/streams/developer-guide/config-streams.html b/21/documentation/streams/developer-guide/config-streams.html
new file mode 100644
index 0000000..979f66d
--- /dev/null
+++ b/21/documentation/streams/developer-guide/config-streams.html
@@ -0,0 +1,19 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- should always link the latest release's documentation -->
+<!--#include virtual="../../../streams/developer-guide/config-streams.html" -->
diff --git a/21/documentation/streams/developer-guide/datatypes.html b/21/documentation/streams/developer-guide/datatypes.html
new file mode 100644
index 0000000..98dd3a1
--- /dev/null
+++ b/21/documentation/streams/developer-guide/datatypes.html
@@ -0,0 +1,19 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- should always link the latest release's documentation -->
+<!--#include virtual="../../../streams/developer-guide/datatypes.html" -->
diff --git a/21/documentation/streams/developer-guide/dsl-api.html b/21/documentation/streams/developer-guide/dsl-api.html
new file mode 100644
index 0000000..1bbc06d
--- /dev/null
+++ b/21/documentation/streams/developer-guide/dsl-api.html
@@ -0,0 +1,19 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- should always link the latest release's documentation -->
+<!--#include virtual="../../../streams/developer-guide/dsl-api.html" -->
diff --git a/21/documentation/streams/developer-guide/index.html b/21/documentation/streams/developer-guide/index.html
new file mode 100644
index 0000000..3a61247
--- /dev/null
+++ b/21/documentation/streams/developer-guide/index.html
@@ -0,0 +1,19 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- should always link the latest release's documentation -->
+<!--#include virtual="../../../streams/developer-guide/index.html" -->
diff --git a/21/documentation/streams/developer-guide/interactive-queries.html b/21/documentation/streams/developer-guide/interactive-queries.html
new file mode 100644
index 0000000..0506012
--- /dev/null
+++ b/21/documentation/streams/developer-guide/interactive-queries.html
@@ -0,0 +1,19 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- should always link the latest release's documentation -->
+<!--#include virtual="../../../streams/developer-guide/interactive-queries.html" -->
diff --git a/21/documentation/streams/developer-guide/manage-topics.html b/21/documentation/streams/developer-guide/manage-topics.html
new file mode 100644
index 0000000..f422554
--- /dev/null
+++ b/21/documentation/streams/developer-guide/manage-topics.html
@@ -0,0 +1,19 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- should always link the latest release's documentation -->
+<!--#include virtual="../../../streams/developer-guide/manage-topics.html" -->
diff --git a/21/documentation/streams/developer-guide/memory-mgmt.html b/21/documentation/streams/developer-guide/memory-mgmt.html
new file mode 100644
index 0000000..024e137
--- /dev/null
+++ b/21/documentation/streams/developer-guide/memory-mgmt.html
@@ -0,0 +1,19 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- should always link the latest release's documentation -->
+<!--#include virtual="../../../streams/developer-guide/memory-mgmt.html" -->
diff --git a/21/documentation/streams/developer-guide/processor-api.html b/21/documentation/streams/developer-guide/processor-api.html
new file mode 100644
index 0000000..9e9ab91
--- /dev/null
+++ b/21/documentation/streams/developer-guide/processor-api.html
@@ -0,0 +1,19 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- should always link the latest release's documentation -->
+<!--#include virtual="../../../streams/developer-guide/processor-api.html" -->
diff --git a/21/documentation/streams/developer-guide/running-app.html b/21/documentation/streams/developer-guide/running-app.html
new file mode 100644
index 0000000..05d5f0b
--- /dev/null
+++ b/21/documentation/streams/developer-guide/running-app.html
@@ -0,0 +1,19 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- should always link the latest release's documentation -->
+<!--#include virtual="../../../streams/developer-guide/running-app.html" -->
diff --git a/21/documentation/streams/developer-guide/security.html b/21/documentation/streams/developer-guide/security.html
new file mode 100644
index 0000000..5d6e5f0
--- /dev/null
+++ b/21/documentation/streams/developer-guide/security.html
@@ -0,0 +1,19 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- should always link the latest release's documentation -->
+<!--#include virtual="../../../streams/developer-guide/security.html" -->
diff --git a/21/documentation/streams/developer-guide/testing.html b/21/documentation/streams/developer-guide/testing.html
new file mode 100644
index 0000000..4753e66
--- /dev/null
+++ b/21/documentation/streams/developer-guide/testing.html
@@ -0,0 +1,19 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- should always link the latest release's documentation -->
+<!--#include virtual="../../../streams/developer-guide/testing.html" -->
diff --git a/21/documentation/streams/developer-guide/write-streams.html b/21/documentation/streams/developer-guide/write-streams.html
new file mode 100644
index 0000000..976c6fe
--- /dev/null
+++ b/21/documentation/streams/developer-guide/write-streams.html
@@ -0,0 +1,19 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- should always link the latest release's documentation -->
+<!--#include virtual="../../../streams/developer-guide/write-streams.html" -->
diff --git a/21/documentation/streams/index.html b/21/documentation/streams/index.html
new file mode 100644
index 0000000..5ff3b3b
--- /dev/null
+++ b/21/documentation/streams/index.html
@@ -0,0 +1,19 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- should always link the latest release's documentation -->
+<!--#include virtual="../../streams/index.html" -->
diff --git a/21/documentation/streams/quickstart.html b/21/documentation/streams/quickstart.html
new file mode 100644
index 0000000..efb0234
--- /dev/null
+++ b/21/documentation/streams/quickstart.html
@@ -0,0 +1,19 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- should always link the latest release's documentation -->
+<!--#include virtual="../../streams/quickstart.html" -->
diff --git a/21/documentation/streams/tutorial.html b/21/documentation/streams/tutorial.html
new file mode 100644
index 0000000..e2cf401
--- /dev/null
+++ b/21/documentation/streams/tutorial.html
@@ -0,0 +1,19 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- should always link the latest release's documentation -->
+<!--#include virtual="../../streams/tutorial.html" -->
diff --git a/21/documentation/streams/upgrade-guide.html b/21/documentation/streams/upgrade-guide.html
new file mode 100644
index 0000000..b1b3200
--- /dev/null
+++ b/21/documentation/streams/upgrade-guide.html
@@ -0,0 +1,19 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- should always link the latest release's documentation -->
+<!--#include virtual="../../streams/upgrade-guide.html" -->
diff --git a/21/ecosystem.html b/21/ecosystem.html
new file mode 100644
index 0000000..5fbcec5
--- /dev/null
+++ b/21/ecosystem.html
@@ -0,0 +1,18 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+There are a plethora of tools that integrate with Kafka outside the main distribution. The <a href="https://cwiki.apache.org/confluence/display/KAFKA/Ecosystem"> ecosystem page</a> lists many of these, including stream processing systems, Hadoop integration, monitoring, and deployment tools.
diff --git a/21/generated/admin_client_config.html b/21/generated/admin_client_config.html
new file mode 100644
index 0000000..e69de29
diff --git a/21/generated/connect_config.html b/21/generated/connect_config.html
new file mode 100644
index 0000000..e69de29
diff --git a/21/generated/connect_metrics.html b/21/generated/connect_metrics.html
new file mode 100644
index 0000000..e69de29
diff --git a/21/generated/connect_transforms.html b/21/generated/connect_transforms.html
new file mode 100644
index 0000000..e69de29
diff --git a/21/generated/consumer_config.html b/21/generated/consumer_config.html
new file mode 100644
index 0000000..e69de29
diff --git a/21/generated/consumer_metrics.html b/21/generated/consumer_metrics.html
new file mode 100644
index 0000000..e69de29
diff --git a/21/generated/kafka_config.html b/21/generated/kafka_config.html
new file mode 100644
index 0000000..e69de29
diff --git a/21/generated/producer_config.html b/21/generated/producer_config.html
new file mode 100644
index 0000000..e69de29
diff --git a/21/generated/producer_metrics.html b/21/generated/producer_metrics.html
new file mode 100644
index 0000000..e69de29
diff --git a/21/generated/protocol_api_keys.html b/21/generated/protocol_api_keys.html
new file mode 100644
index 0000000..e69de29
diff --git a/21/generated/protocol_errors.html b/21/generated/protocol_errors.html
new file mode 100644
index 0000000..e69de29
diff --git a/21/generated/protocol_messages.html b/21/generated/protocol_messages.html
new file mode 100644
index 0000000..e69de29
diff --git a/21/generated/protocol_types.html b/21/generated/protocol_types.html
new file mode 100644
index 0000000..e69de29
diff --git a/21/generated/sink_connector_config.html b/21/generated/sink_connector_config.html
new file mode 100644
index 0000000..e69de29
diff --git a/21/generated/source_connector_config.html b/21/generated/source_connector_config.html
new file mode 100644
index 0000000..e69de29
diff --git a/21/generated/streams_config.html b/21/generated/streams_config.html
new file mode 100644
index 0000000..e69de29
diff --git a/21/generated/topic_config.html b/21/generated/topic_config.html
new file mode 100644
index 0000000..e69de29
diff --git a/21/images/consumer-groups.png b/21/images/consumer-groups.png
new file mode 100644
index 0000000..16fe293
Binary files /dev/null and b/21/images/consumer-groups.png differ
diff --git a/21/images/icons/NYT.jpg b/21/images/icons/NYT.jpg
new file mode 100644
index 0000000..f4a7e8f
Binary files /dev/null and b/21/images/icons/NYT.jpg differ
diff --git a/21/images/icons/architecture--white.png b/21/images/icons/architecture--white.png
new file mode 100644
index 0000000..98b1b03
Binary files /dev/null and b/21/images/icons/architecture--white.png differ
diff --git a/21/images/icons/architecture.png b/21/images/icons/architecture.png
new file mode 100644
index 0000000..6f9fd40
Binary files /dev/null and b/21/images/icons/architecture.png differ
diff --git a/21/images/icons/documentation--white.png b/21/images/icons/documentation--white.png
new file mode 100644
index 0000000..1e8fd97
Binary files /dev/null and b/21/images/icons/documentation--white.png differ
diff --git a/21/images/icons/documentation.png b/21/images/icons/documentation.png
new file mode 100644
index 0000000..8d9da19
Binary files /dev/null and b/21/images/icons/documentation.png differ
diff --git a/21/images/icons/line.png b/21/images/icons/line.png
new file mode 100755
index 0000000..4587d21
Binary files /dev/null and b/21/images/icons/line.png differ
diff --git a/21/images/icons/new-york.png b/21/images/icons/new-york.png
new file mode 100755
index 0000000..42a4b0b
Binary files /dev/null and b/21/images/icons/new-york.png differ
diff --git a/21/images/icons/rabobank.png b/21/images/icons/rabobank.png
new file mode 100755
index 0000000..ddad710
Binary files /dev/null and b/21/images/icons/rabobank.png differ
diff --git a/21/images/icons/tutorials--white.png b/21/images/icons/tutorials--white.png
new file mode 100644
index 0000000..97a0c04
Binary files /dev/null and b/21/images/icons/tutorials--white.png differ
diff --git a/21/images/icons/tutorials.png b/21/images/icons/tutorials.png
new file mode 100644
index 0000000..983da6c
Binary files /dev/null and b/21/images/icons/tutorials.png differ
diff --git a/21/images/icons/zalando.png b/21/images/icons/zalando.png
new file mode 100755
index 0000000..719a7dc
Binary files /dev/null and b/21/images/icons/zalando.png differ
diff --git a/21/images/kafka-apis.png b/21/images/kafka-apis.png
new file mode 100644
index 0000000..db6053c
Binary files /dev/null and b/21/images/kafka-apis.png differ
diff --git a/21/images/kafka_log.png b/21/images/kafka_log.png
new file mode 100644
index 0000000..75abd96
Binary files /dev/null and b/21/images/kafka_log.png differ
diff --git a/21/images/kafka_multidc.png b/21/images/kafka_multidc.png
new file mode 100644
index 0000000..7bc56f4
Binary files /dev/null and b/21/images/kafka_multidc.png differ
diff --git a/21/images/kafka_multidc_complex.png b/21/images/kafka_multidc_complex.png
new file mode 100644
index 0000000..ab88deb
Binary files /dev/null and b/21/images/kafka_multidc_complex.png differ
diff --git a/21/images/log_anatomy.png b/21/images/log_anatomy.png
new file mode 100644
index 0000000..a649499
Binary files /dev/null and b/21/images/log_anatomy.png differ
diff --git a/21/images/log_cleaner_anatomy.png b/21/images/log_cleaner_anatomy.png
new file mode 100644
index 0000000..fb425b0
Binary files /dev/null and b/21/images/log_cleaner_anatomy.png differ
diff --git a/21/images/log_compaction.png b/21/images/log_compaction.png
new file mode 100644
index 0000000..4e4a833
Binary files /dev/null and b/21/images/log_compaction.png differ
diff --git a/21/images/log_consumer.png b/21/images/log_consumer.png
new file mode 100644
index 0000000..fbc45f2
Binary files /dev/null and b/21/images/log_consumer.png differ
diff --git a/21/images/mirror-maker.png b/21/images/mirror-maker.png
new file mode 100644
index 0000000..8f76b1f
Binary files /dev/null and b/21/images/mirror-maker.png differ
diff --git a/21/images/producer_consumer.png b/21/images/producer_consumer.png
new file mode 100644
index 0000000..4b10cc9
Binary files /dev/null and b/21/images/producer_consumer.png differ
diff --git a/21/images/streams-architecture-overview.jpg b/21/images/streams-architecture-overview.jpg
new file mode 100644
index 0000000..9222079
Binary files /dev/null and b/21/images/streams-architecture-overview.jpg differ
diff --git a/21/images/streams-architecture-states.jpg b/21/images/streams-architecture-states.jpg
new file mode 100644
index 0000000..fde12db
Binary files /dev/null and b/21/images/streams-architecture-states.jpg differ
diff --git a/21/images/streams-architecture-tasks.jpg b/21/images/streams-architecture-tasks.jpg
new file mode 100644
index 0000000..2e957f9
Binary files /dev/null and b/21/images/streams-architecture-tasks.jpg differ
diff --git a/21/images/streams-architecture-threads.jpg b/21/images/streams-architecture-threads.jpg
new file mode 100644
index 0000000..d5f10db
Binary files /dev/null and b/21/images/streams-architecture-threads.jpg differ
diff --git a/21/images/streams-architecture-topology.jpg b/21/images/streams-architecture-topology.jpg
new file mode 100644
index 0000000..f42e8cd
Binary files /dev/null and b/21/images/streams-architecture-topology.jpg differ
diff --git a/21/images/streams-cache-and-commit-interval.png b/21/images/streams-cache-and-commit-interval.png
new file mode 100644
index 0000000..a663bc6
Binary files /dev/null and b/21/images/streams-cache-and-commit-interval.png differ
diff --git a/21/images/streams-concepts-topology.jpg b/21/images/streams-concepts-topology.jpg
new file mode 100644
index 0000000..832f6d4
Binary files /dev/null and b/21/images/streams-concepts-topology.jpg differ
diff --git a/21/images/streams-elastic-scaling-1.png b/21/images/streams-elastic-scaling-1.png
new file mode 100644
index 0000000..7823ac1
Binary files /dev/null and b/21/images/streams-elastic-scaling-1.png differ
diff --git a/21/images/streams-elastic-scaling-2.png b/21/images/streams-elastic-scaling-2.png
new file mode 100644
index 0000000..374b5ff
Binary files /dev/null and b/21/images/streams-elastic-scaling-2.png differ
diff --git a/21/images/streams-elastic-scaling-3.png b/21/images/streams-elastic-scaling-3.png
new file mode 100644
index 0000000..0b4adaf
Binary files /dev/null and b/21/images/streams-elastic-scaling-3.png differ
diff --git a/21/images/streams-interactive-queries-01.png b/21/images/streams-interactive-queries-01.png
new file mode 100644
index 0000000..d5d5031
Binary files /dev/null and b/21/images/streams-interactive-queries-01.png differ
diff --git a/21/images/streams-interactive-queries-02.png b/21/images/streams-interactive-queries-02.png
new file mode 100644
index 0000000..ea894b6
Binary files /dev/null and b/21/images/streams-interactive-queries-02.png differ
diff --git a/21/images/streams-interactive-queries-03.png b/21/images/streams-interactive-queries-03.png
new file mode 100644
index 0000000..403e3ae
Binary files /dev/null and b/21/images/streams-interactive-queries-03.png differ
diff --git a/21/images/streams-interactive-queries-api-01.png b/21/images/streams-interactive-queries-api-01.png
new file mode 100644
index 0000000..2b4aaed
Binary files /dev/null and b/21/images/streams-interactive-queries-api-01.png differ
diff --git a/21/images/streams-interactive-queries-api-02.png b/21/images/streams-interactive-queries-api-02.png
new file mode 100644
index 0000000..e5e7527
Binary files /dev/null and b/21/images/streams-interactive-queries-api-02.png differ
diff --git a/21/images/streams-session-windows-01.png b/21/images/streams-session-windows-01.png
new file mode 100644
index 0000000..2d711d8
Binary files /dev/null and b/21/images/streams-session-windows-01.png differ
diff --git a/21/images/streams-session-windows-02.png b/21/images/streams-session-windows-02.png
new file mode 100644
index 0000000..6c0382f
Binary files /dev/null and b/21/images/streams-session-windows-02.png differ
diff --git a/21/images/streams-stateful_operations.png b/21/images/streams-stateful_operations.png
new file mode 100644
index 0000000..b0fe3de
Binary files /dev/null and b/21/images/streams-stateful_operations.png differ
diff --git a/21/images/streams-table-duality-01.png b/21/images/streams-table-duality-01.png
new file mode 100644
index 0000000..4fa4d1b
Binary files /dev/null and b/21/images/streams-table-duality-01.png differ
diff --git a/21/images/streams-table-duality-02.png b/21/images/streams-table-duality-02.png
new file mode 100644
index 0000000..4e805c1
Binary files /dev/null and b/21/images/streams-table-duality-02.png differ
diff --git a/21/images/streams-table-duality-03.png b/21/images/streams-table-duality-03.png
new file mode 100644
index 0000000..b0b04f5
Binary files /dev/null and b/21/images/streams-table-duality-03.png differ
diff --git a/21/images/streams-table-updates-01.png b/21/images/streams-table-updates-01.png
new file mode 100644
index 0000000..3a2c35e
Binary files /dev/null and b/21/images/streams-table-updates-01.png differ
diff --git a/21/images/streams-table-updates-02.png b/21/images/streams-table-updates-02.png
new file mode 100644
index 0000000..a0a5b1f
Binary files /dev/null and b/21/images/streams-table-updates-02.png differ
diff --git a/21/images/streams-time-windows-hopping.png b/21/images/streams-time-windows-hopping.png
new file mode 100644
index 0000000..5fcb9d2
Binary files /dev/null and b/21/images/streams-time-windows-hopping.png differ
diff --git a/21/images/streams-time-windows-tumbling.png b/21/images/streams-time-windows-tumbling.png
new file mode 100644
index 0000000..571ab79
Binary files /dev/null and b/21/images/streams-time-windows-tumbling.png differ
diff --git a/21/images/streams-welcome.png b/21/images/streams-welcome.png
new file mode 100644
index 0000000..63918c4
Binary files /dev/null and b/21/images/streams-welcome.png differ
diff --git a/21/images/tracking_high_level.png b/21/images/tracking_high_level.png
new file mode 100644
index 0000000..b643230
Binary files /dev/null and b/21/images/tracking_high_level.png differ
diff --git a/21/implementation.html b/21/implementation.html
new file mode 100644
index 0000000..cc2b0f4
--- /dev/null
+++ b/21/implementation.html
@@ -0,0 +1,305 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<script id="implementation-template" type="text/x-handlebars-template">
+    <h3><a id="networklayer" href="#networklayer">5.1 Network Layer</a></h3>
+    <p>
+    The network layer is a fairly straight-forward NIO server, and will not be described in great detail. The sendfile implementation is done by giving the <code>MessageSet</code> interface a <code>writeTo</code> method. This allows the file-backed message set to use the more efficient <code>transferTo</code> implementation instead of an in-process buffered write. The threading model is a single acceptor thread and <i>N</i> processor threads which handle a fixed number of connections eac [...]
+    </p>
+    <h3><a id="messages" href="#messages">5.2 Messages</a></h3>
+    <p>
+    Messages consist of a variable-length header, a variable length opaque key byte array and a variable length opaque value byte array. The format of the header is described in the following section.
+    Leaving the key and value opaque is the right decision: there is a great deal of progress being made on serialization libraries right now, and any particular choice is unlikely to be right for all uses. Needless to say a particular application using Kafka would likely mandate a particular serialization type as part of its usage. The <code>RecordBatch</code> interface is simply an iterator over messages with specialized methods for bulk reading and writing to an NIO <code>Channel</code>.</p>
+
+    <h3><a id="messageformat" href="#messageformat">5.3 Message Format</a></h3>
+    <p>
+    Messages (aka Records) are always written in batches. The technical term for a batch of messages is a record batch, and a record batch contains one or more records. In the degenerate case, we could have a record batch containing a single record.
+    Record batches and records have their own headers. The format of each is described below. </p>
+
+    <h4><a id="recordbatch" href="#recordbatch">5.3.1 Record Batch</a></h4>
+	<p> The following is the on-disk format of a RecordBatch. </p>
+	<p><pre class="brush: java;">
+		baseOffset: int64
+		batchLength: int32
+		partitionLeaderEpoch: int32
+		magic: int8 (current magic value is 2)
+		crc: int32
+		attributes: int16
+			bit 0~2:
+				0: no compression
+				1: gzip
+				2: snappy
+				3: lz4
+				4: zstd
+			bit 3: timestampType
+			bit 4: isTransactional (0 means not transactional)
+			bit 5: isControlBatch (0 means not a control batch)
+			bit 6~15: unused
+		lastOffsetDelta: int32
+		firstTimestamp: int64
+		maxTimestamp: int64
+		producerId: int64
+		producerEpoch: int16
+		baseSequence: int32
+		records: [Record]
+	</pre></p>
+    <p> Note that when compression is enabled, the compressed record data is serialized directly following the count of the number of records. </p>
+
+    <p>The CRC covers the data from the attributes to the end of the batch (i.e. all the bytes that follow the CRC). It is located after the magic byte, which
+    means that clients must parse the magic byte before deciding how to interpret the bytes between the batch length and the magic byte. The partition leader
+    epoch field is not included in the CRC computation to avoid the need to recompute the CRC when this field is assigned for every batch that is received by
+    the broker. The CRC-32C (Castagnoli) polynomial is used for the computation.</p>
+
+    <p>On compaction: unlike the older message formats, magic v2 and above preserves the first and last offset/sequence numbers from the original batch when the log is cleaned. This is required in order to be able to restore the
+    producer's state when the log is reloaded. If we did not retain the last sequence number, for example, then after a partition leader failure, the producer might see an OutOfSequence error. The base sequence number must
+    be preserved for duplicate checking (the broker checks incoming Produce requests for duplicates by verifying that the first and last sequence numbers of the incoming batch match the last from that producer). As a result,
+    it is possible to have empty batches in the log when all the records in the batch are cleaned but batch is still retained in order to preserve a producer's last sequence number. One oddity here is that the baseTimestamp
+    field is not preserved during compaction, so it will change if the first record in the batch is compacted away.</p>
+
+    <h5><a id="controlbatch" href="#controlbatch">5.3.1.1 Control Batches</a></h5>
+    <p>A control batch contains a single record called the control record. Control records should not be passed on to applications. Instead, they are used by consumers to filter out aborted transactional messages.</p>
+    <p> The key of a control record conforms to the following schema: </p>
+    <p><pre class="brush: java">
+       version: int16 (current version is 0)
+       type: int16 (0 indicates an abort marker, 1 indicates a commit)
+    </pre></p>
+    <p>The schema for the value of a control record is dependent on the type. The value is opaque to clients.</p>
+
+    <h4><a id="record" href="#record">5.3.2 Record</a></h4>
+	<p>Record level headers were introduced in Kafka 0.11.0. The on-disk format of a record with Headers is delineated below. </p>
+	<p><pre class="brush: java;">
+		length: varint
+		attributes: int8
+			bit 0~7: unused
+		timestampDelta: varint
+		offsetDelta: varint
+		keyLength: varint
+		key: byte[]
+		valueLen: varint
+		value: byte[]
+		Headers => [Header]
+	</pre></p>
+	<h5><a id="recordheader" href="#recordheader">5.3.2.1 Record Header</a></h5>
+	<p><pre class="brush: java;">
+		headerKeyLength: varint
+		headerKey: String
+		headerValueLength: varint
+		Value: byte[]
+	</pre></p>
+    <p>We use the same varint encoding as Protobuf. More information on the latter can be found <a href="https://developers.google.com/protocol-buffers/docs/encoding#varints">here</a>. The count of headers in a record
+    is also encoded as a varint.</p>
+
+    <h4><a id="messageset" href="#messageset">5.3.3 Old Message Format</a></h4>
+    <p>
+        Prior to Kafka 0.11, messages were transferred and stored in <i>message sets</i>. In a message set, each message has its own metadata. Note that although message sets are represented as an array,
+        they are not preceded by an int32 array size like other array elements in the protocol.
+    </p>
+
+    <b>Message Set:</b><br>
+    <p><pre class="brush: java;">
+    MessageSet (Version: 0) => [offset message_size message]
+        offset => INT64
+        message_size => INT32
+        message => crc magic_byte attributes key value
+            crc => INT32
+            magic_byte => INT8
+            attributes => INT8
+                bit 0~2:
+                    0: no compression
+                    1: gzip
+                    2: snappy
+                bit 3~7: unused
+            key => BYTES
+            value => BYTES
+    </pre></p>
+    <p><pre class="brush: java;">
+    MessageSet (Version: 1) => [offset message_size message]
+        offset => INT64
+        message_size => INT32
+        message => crc magic_byte attributes key value
+            crc => INT32
+            magic_byte => INT8
+            attributes => INT8
+                bit 0~2:
+                    0: no compression
+                    1: gzip
+                    2: snappy
+                    3: lz4
+                bit 3: timestampType
+                    0: create time
+                    1: log append time
+                bit 4~7: unused
+            timestamp =>INT64
+            key => BYTES
+            value => BYTES
+    </pre></p>
+    <p>
+        In versions prior to Kafka 0.10, the only supported message format version (which is indicated in the magic value) was 0. Message format version 1 was introduced with timestamp support in version 0.10.
+    <ul>
+        <li>Similarly to version 2 above, the lowest bits of attributes represent the compression type.</li>
+        <li>In version 1, the producer should always set the timestamp type bit to 0. If the topic is configured to use log append time,
+            (through either broker level config log.message.timestamp.type = LogAppendTime or topic level config message.timestamp.type = LogAppendTime),
+	    the broker will overwrite the timestamp type and the timestamp in the message set.</li>
+        <li>The highest bits of attributes must be set to 0.</li>
+    </ul>
+    </p>
+    <p>In message format versions 0 and 1 Kafka supports recursive messages to enable compression. In this case the message's attributes must be set
+      to indicate one of the compression types and the value field will contain a message set compressed with that type. We often refer
+      to the nested messages as "inner messages" and the wrapping message as the "outer message." Note that the key should be null
+      for the outer message and its offset will be the offset of the last inner message.
+    </p>
+    <p>When receiving recursive version 0 messages, the broker decompresses them and each inner message is assigned an offset individually.
+      In version 1, to avoid server side re-compression, only the wrapper message will be assigned an offset. The inner messages
+      will have relative offsets. The absolute offset can be computed using the offset from the outer message, which corresponds
+      to the offset assigned to the last inner message.
+    </p>
+
+    <p>The crc field contains the CRC32 (and not CRC-32C) of the subsequent message bytes (i.e. from magic byte to the value).</p>
+
+    <h3><a id="log" href="#log">5.4 Log</a></h3>
+    <p>
+    A log for a topic named "my_topic" with two partitions consists of two directories (namely <code>my_topic_0</code> and <code>my_topic_1</code>) populated with data files containing the messages for that topic. The format of the log files is a sequence of "log entries""; each log entry is a 4 byte integer <i>N</i> storing the message length which is followed by the <i>N</i> message bytes. Each message is uniquely identified by a 64-bit integer <i>offset</i> giving the byte position of [...]
+    </p>
+    <p>
+    The exact binary format for records is versioned and maintained as a standard interface so record batches can be transferred between producer, broker, and client without recopying or conversion when desirable. The previous section included details about the on-disk format of records.</p>
+    </p>
+   <p>
+    The use of the message offset as the message id is unusual. Our original idea was to use a GUID generated by the producer, and maintain a mapping from GUID to offset on each broker. But since a consumer must maintain an ID for each server, the global uniqueness of the GUID provides no value. Furthermore, the complexity of maintaining the mapping from a random id to an offset requires a heavy weight index structure which must be synchronized with disk, essentially requiring a full per [...]
+    </p>
+    <img class="centered" src="/{{version}}/images/kafka_log.png">
+    <h4><a id="impl_writes" href="#impl_writes">Writes</a></h4>
+    <p>
+    The log allows serial appends which always go to the last file. This file is rolled over to a fresh file when it reaches a configurable size (say 1GB). The log takes two configuration parameters: <i>M</i>, which gives the number of messages to write before forcing the OS to flush the file to disk, and <i>S</i>, which gives a number of seconds after which a flush is forced. This gives a durability guarantee of losing at most <i>M</i> messages or <i>S</i> seconds of data in the event o [...]
+    </p>
+    <h4><a id="impl_reads" href="#impl_reads">Reads</a></h4>
+    <p>
+    Reads are done by giving the 64-bit logical offset of a message and an <i>S</i>-byte max chunk size. This will return an iterator over the messages contained in the <i>S</i>-byte buffer. <i>S</i> is intended to be larger than any single message, but in the event of an abnormally large message, the read can be retried multiple times, each time doubling the buffer size, until the message is read successfully. A maximum message and buffer size can be specified to make the server reject  [...]
+    </p>
+    <p>
+    The actual process of reading from an offset requires first locating the log segment file in which the data is stored, calculating the file-specific offset from the global offset value, and then reading from that file offset. The search is done as a simple binary search variation against an in-memory range maintained for each file.
+    </p>
+    <p>
+    The log provides the capability of getting the most recently written message to allow clients to start subscribing as of "right now". This is also useful in the case the consumer fails to consume its data within its SLA-specified number of days. In this case when the client attempts to consume a non-existent offset it is given an OutOfRangeException and can either reset itself or fail as appropriate to the use case.
+    </p>
+
+    <p> The following is the format of the results sent to the consumer.
+
+    <pre class="brush: text;">
+    MessageSetSend (fetch result)
+
+    total length     : 4 bytes
+    error code       : 2 bytes
+    message 1        : x bytes
+    ...
+    message n        : x bytes
+    </pre>
+
+    <pre class="brush: text;">
+    MultiMessageSetSend (multiFetch result)
+
+    total length       : 4 bytes
+    error code         : 2 bytes
+    messageSetSend 1
+    ...
+    messageSetSend n
+    </pre>
+    <h4><a id="impl_deletes" href="#impl_deletes">Deletes</a></h4>
+    <p>
+    Data is deleted one log segment at a time. The log manager allows pluggable delete policies to choose which files are eligible for deletion. The current policy deletes any log with a modification time of more than <i>N</i> days ago, though a policy which retained the last <i>N</i> GB could also be useful. To avoid locking reads while still allowing deletes that modify the segment list we use a copy-on-write style segment list implementation that provides consistent views to allow a b [...]
+    </p>
+    <h4><a id="impl_guarantees" href="#impl_guarantees">Guarantees</a></h4>
+    <p>
+    The log provides a configuration parameter <i>M</i> which controls the maximum number of messages that are written before forcing a flush to disk. On startup a log recovery process is run that iterates over all messages in the newest log segment and verifies that each message entry is valid. A message entry is valid if the sum of its size and offset are less than the length of the file AND the CRC32 of the message payload matches the CRC stored with the message. In the event corrupti [...]
+    </p>
+    <p>
+    Note that two kinds of corruption must be handled: truncation in which an unwritten block is lost due to a crash, and corruption in which a nonsense block is ADDED to the file. The reason for this is that in general the OS makes no guarantee of the write order between the file inode and the actual block data so in addition to losing written data the file can gain nonsense data if the inode is updated with a new size but a crash occurs before the block containing that data is written. [...]
+    </p>
+
+    <h3><a id="distributionimpl" href="#distributionimpl">5.5 Distribution</a></h3>
+    <h4><a id="impl_offsettracking" href="#impl_offsettracking">Consumer Offset Tracking</a></h4>
+    <p>
+    Kafka consumer tracks the maximum offset it has consumed in each partition and has the capability to commit offsets so
+        that it can resume from those offsets in the event of a restart. Kafka provides the option to store all the offsets for
+        a given consumer group in a designated broker (for that group) called the group coordinator.  i.e., any consumer instance
+        in that consumer group should send its offset commits and fetches to that group coordinator (broker). Consumer groups are
+        assigned to coordinators based on their group names. A consumer can look up its coordinator by issuing a FindCoordinatorRequest
+        to any Kafka broker and reading the FindCoordinatorResponse which will contain the coordinator details. The consumer
+        can then proceed to commit or fetch offsets from the coordinator broker. In case the coordinator moves, the consumer will
+        need to rediscover the coordinator. Offset commits can be done automatically or manually by consumer instance.
+    </p>
+
+    <p>
+    When the group coordinator receives an OffsetCommitRequest, it appends the request to a special <a href="#compaction">compacted</a> Kafka topic named <i>__consumer_offsets</i>.
+        The broker sends a successful offset commit response to the consumer only after all the replicas of the offsets topic receive the offsets.
+        In case the offsets fail to replicate within a configurable timeout, the offset commit will fail and the consumer may retry the commit after backing off.
+        The brokers periodically compact the offsets topic since it only needs to maintain the most recent offset commit per partition.
+        The coordinator also caches the offsets in an in-memory table in order to serve offset fetches quickly.
+    </p>
+
+    <p>
+    When the coordinator receives an offset fetch request, it simply returns the last committed offset vector from the offsets cache.
+        In case coordinator was just started or if it just became the coordinator for a new set of consumer groups (by becoming a leader for a partition of the offsets topic),
+        it may need to load the offsets topic partition into the cache. In this case, the offset fetch will fail with an
+        CoordinatorLoadInProgressException and the consumer may retry the OffsetFetchRequest after backing off.
+    </p>
+
+    <h4><a id="impl_zookeeper" href="#impl_zookeeper">ZooKeeper Directories</a></h4>
+    <p>
+    The following gives the ZooKeeper structures and algorithms used for co-ordination between consumers and brokers.
+    </p>
+
+    <h4><a id="impl_zknotation" href="#impl_zknotation">Notation</a></h4>
+    <p>
+    When an element in a path is denoted [xyz], that means that the value of xyz is not fixed and there is in fact a ZooKeeper znode for each possible value of xyz. For example /topics/[topic] would be a directory named /topics containing a sub-directory for each topic name. Numerical ranges are also given such as [0...5] to indicate the subdirectories 0, 1, 2, 3, 4. An arrow -> is used to indicate the contents of a znode. For example /hello -> world would indicate a znode /hello contain [...]
+    </p>
+
+    <h4><a id="impl_zkbroker" href="#impl_zkbroker">Broker Node Registry</a></h4>
+    <pre class="brush: json;">
+    /brokers/ids/[0...N] --> {"jmx_port":...,"timestamp":...,"endpoints":[...],"host":...,"version":...,"port":...} (ephemeral node)
+    </pre>
+    <p>
+    This is a list of all present broker nodes, each of which provides a unique logical broker id which identifies it to consumers (which must be given as part of its configuration). On startup, a broker node registers itself by creating a znode with the logical broker id under /brokers/ids. The purpose of the logical broker id is to allow a broker to be moved to a different physical machine without affecting consumers. An attempt to register a broker id that is already in use (say becau [...]
+    </p>
+    <p>
+    Since the broker registers itself in ZooKeeper using ephemeral znodes, this registration is dynamic and will disappear if the broker is shutdown or dies (thus notifying consumers it is no longer available).
+    </p>
+    <h4><a id="impl_zktopic" href="#impl_zktopic">Broker Topic Registry</a></h4>
+    <pre class="brush: json;">
+    /brokers/topics/[topic]/partitions/[0...N]/state --> {"controller_epoch":...,"leader":...,"version":...,"leader_epoch":...,"isr":[...]} (ephemeral node)
+    </pre>
+
+    <p>
+    Each broker registers itself under the topics it maintains and stores the number of partitions for that topic.
+    </p>
+
+    <h4><a id="impl_clusterid" href="#impl_clusterid">Cluster Id</a></h4>
+
+    <p>
+        The cluster id is a unique and immutable identifier assigned to a Kafka cluster. The cluster id can have a maximum of 22 characters and the allowed characters are defined by the regular expression [a-zA-Z0-9_\-]+, which corresponds to the characters used by the URL-safe Base64 variant with no padding. Conceptually, it is auto-generated when a cluster is started for the first time.
+    </p>
+    <p>
+        Implementation-wise, it is generated when a broker with version 0.10.1 or later is successfully started for the first time. The broker tries to get the cluster id from the <code>/cluster/id</code> znode during startup. If the znode does not exist, the broker generates a new cluster id and creates the znode with this cluster id.
+    </p>
+
+    <h4><a id="impl_brokerregistration" href="#impl_brokerregistration">Broker node registration</a></h4>
+
+    <p>
+    The broker nodes are basically independent, so they only publish information about what they have. When a broker joins, it registers itself under the broker node registry directory and writes information about its host name and port. The broker also register the list of existing topics and their logical partitions in the broker topic registry. New topics are registered dynamically when they are created on the broker.
+    </p>
+</script>
+
+<div class="p-implementation"></div>
diff --git a/21/introduction.html b/21/introduction.html
new file mode 100644
index 0000000..7f4c3e2
--- /dev/null
+++ b/21/introduction.html
@@ -0,0 +1,214 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<script><!--#include virtual="js/templateData.js" --></script>
+
+<script id="introduction-template" type="text/x-handlebars-template">
+  <h3> Apache Kafka&reg; is <i>a distributed streaming platform</i>. What exactly does that mean?</h3>
+  <p>A streaming platform has three key capabilities:</p>
+  <ul>
+    <li>Publish and subscribe to streams of records, similar to a message queue or enterprise messaging system.
+    <li>Store streams of records in a fault-tolerant durable way.
+    <li>Process streams of records as they occur.
+  </ul>
+  <p>Kafka is generally used for two broad classes of applications:</p>
+  <ul>
+    <li>Building real-time streaming data pipelines that reliably get data between systems or applications
+    <li>Building real-time streaming applications that transform or react to the streams of data
+  </ul>
+  <p>To understand how Kafka does these things, let's dive in and explore Kafka's capabilities from the bottom up.</p>
+  <p>First a few concepts:</p>
+  <ul>
+    <li>Kafka is run as a cluster on one or more servers that can span multiple datacenters.
+      <li>The Kafka cluster stores streams of <i>records</i> in categories called <i>topics</i>.
+    <li>Each record consists of a key, a value, and a timestamp.
+  </ul>
+  <p>Kafka has four core APIs:</p>
+  <div style="overflow: hidden;">
+      <ul style="float: left; width: 40%;">
+      <li>The <a href="/documentation.html#producerapi">Producer API</a> allows an application to publish a stream of records to one or more Kafka topics.
+      <li>The <a href="/documentation.html#consumerapi">Consumer API</a> allows an application to subscribe to one or more topics and process the stream of records produced to them.
+    <li>The <a href="/documentation/streams">Streams API</a> allows an application to act as a <i>stream processor</i>, consuming an input stream from one or more topics and producing an output stream to one or more output topics, effectively transforming the input streams to output streams.
+    <li>The <a href="/documentation.html#connect">Connector API</a> allows building and running reusable producers or consumers that connect Kafka topics to existing applications or data systems. For example, a connector to a relational database might capture every change to a table.
+  </ul>
+      <img src="/{{version}}/images/kafka-apis.png" style="float: right; width: 50%;">
+      </div>
+  <p>
+  In Kafka the communication between the clients and the servers is done with a simple, high-performance, language agnostic <a href="https://kafka.apache.org/protocol.html">TCP protocol</a>. This protocol is versioned and maintains backwards compatibility with older version. We provide a Java client for Kafka, but clients are available in <a href="https://cwiki.apache.org/confluence/display/KAFKA/Clients">many languages</a>.</p>
+
+  <h4><a id="intro_topics" href="#intro_topics">Topics and Logs</a></h4>
+  <p>Let's first dive into the core abstraction Kafka provides for a stream of records&mdash;the topic.</p>
+  <p>A topic is a category or feed name to which records are published. Topics in Kafka are always multi-subscriber; that is, a topic can have zero, one, or many consumers that subscribe to the data written to it.</p>
+  <p> For each topic, the Kafka cluster maintains a partitioned log that looks like this: </p>
+  <img class="centered" src="/{{version}}/images/log_anatomy.png">
+
+  <p> Each partition is an ordered, immutable sequence of records that is continually appended to&mdash;a structured commit log. The records in the partitions are each assigned a sequential id number called the <i>offset</i> that uniquely identifies each record within the partition.
+  </p>
+  <p>
+  The Kafka cluster durably persists all published records&mdash;whether or not they have been consumed&mdash;using a configurable retention period. For example, if the retention policy is set to two days, then for the two days after a record is published, it is available for consumption, after which it will be discarded to free up space. Kafka's performance is effectively constant with respect to data size so storing data for a long time is not a problem.
+  </p>
+  <img class="centered" src="/{{version}}/images/log_consumer.png" style="width:400px">
+  <p>
+  In fact, the only metadata retained on a per-consumer basis is the offset or position of that consumer in the log. This offset is controlled by the consumer: normally a consumer will advance its offset linearly as it reads records, but, in fact, since the position is controlled by the consumer it can consume records in any order it likes. For example a consumer can reset to an older offset to reprocess data from the past or skip ahead to the most recent record and start consuming from "now".
+  </p>
+  <p>
+  This combination of features means that Kafka consumers are very cheap&mdash;they can come and go without much impact on the cluster or on other consumers. For example, you can use our command line tools to "tail" the contents of any topic without changing what is consumed by any existing consumers.
+  </p>
+  <p>
+  The partitions in the log serve several purposes. First, they allow the log to scale beyond a size that will fit on a single server. Each individual partition must fit on the servers that host it, but a topic may have many partitions so it can handle an arbitrary amount of data. Second they act as the unit of parallelism&mdash;more on that in a bit.
+  </p>
+
+  <h4><a id="intro_distribution" href="#intro_distribution">Distribution</a></h4>
+
+  <p>
+  The partitions of the log are distributed over the servers in the Kafka cluster with each server handling data and requests for a share of the partitions. Each partition is replicated across a configurable number of servers for fault tolerance.
+  </p>
+  <p>
+  Each partition has one server which acts as the "leader" and zero or more servers which act as "followers". The leader handles all read and write requests for the partition while the followers passively replicate the leader. If the leader fails, one of the followers will automatically become the new leader. Each server acts as a leader for some of its partitions and a follower for others so load is well balanced within the cluster.
+  </p>
+
+  <h4><a id="intro_geo-replication" href="#intro_geo-replication">Geo-Replication</a></h4>
+
+  <p>Kafka MirrorMaker provides geo-replication support for your clusters. With MirrorMaker, messages are replicated across multiple datacenters or cloud regions. You can use this in active/passive scenarios for backup and recovery; or in active/active scenarios to place data closer to your users, or support data locality requirements. </p>
+
+  <h4><a id="intro_producers" href="#intro_producers">Producers</a></h4>
+  <p>
+  Producers publish data to the topics of their choice. The producer is responsible for choosing which record to assign to which partition within the topic. This can be done in a round-robin fashion simply to balance load or it can be done according to some semantic partition function (say based on some key in the record). More on the use of partitioning in a second!
+  </p>
+
+  <h4><a id="intro_consumers" href="#intro_consumers">Consumers</a></h4>
+
+  <p>
+  Consumers label themselves with a <i>consumer group</i> name, and each record published to a topic is delivered to one consumer instance within each subscribing consumer group. Consumer instances can be in separate processes or on separate machines.
+  </p>
+  <p>
+  If all the consumer instances have the same consumer group, then the records will effectively be load balanced over the consumer instances.</p>
+  <p>
+  If all the consumer instances have different consumer groups, then each record will be broadcast to all the consumer processes.
+  </p>
+  <img class="centered" src="/{{version}}/images/consumer-groups.png">
+  <p>
+    A two server Kafka cluster hosting four partitions (P0-P3) with two consumer groups. Consumer group A has two consumer instances and group B has four.
+  </p>
+
+  <p>
+  More commonly, however, we have found that topics have a small number of consumer groups, one for each "logical subscriber". Each group is composed of many consumer instances for scalability and fault tolerance. This is nothing more than publish-subscribe semantics where the subscriber is a cluster of consumers instead of a single process.
+  </p>
+  <p>
+  The way consumption is implemented in Kafka is by dividing up the partitions in the log over the consumer instances so that each instance is the exclusive consumer of a "fair share" of partitions at any point in time. This process of maintaining membership in the group is handled by the Kafka protocol dynamically. If new instances join the group they will take over some partitions from other members of the group; if an instance dies, its partitions will be distributed to the remaining  [...]
+  </p>
+  <p>
+  Kafka only provides a total order over records <i>within</i> a partition, not between different partitions in a topic. Per-partition ordering combined with the ability to partition data by key is sufficient for most applications. However, if you require a total order over records this can be achieved with a topic that has only one partition, though this will mean only one consumer process per consumer group.
+  </p>
+  <h4><a id="intro_multi-tenancy" href="#intro_multi-tenancy">Multi-tenancy</a></h4>
+  <p>You can deploy Kafka as a multi-tenant solution. Multi-tenancy is enabled by configuring which topics can produce or consume data. There is also operations support for quotas.  Administrators can define and enforce quotas on requests to control the broker resources that are used by clients.  For more information, see the <a href="https://kafka.apache.org/documentation/#security">security documentation</a>. </p>
+  <h4><a id="intro_guarantees" href="#intro_guarantees">Guarantees</a></h4>
+  <p>
+  At a high-level Kafka gives the following guarantees:
+  </p>
+  <ul>
+    <li>Messages sent by a producer to a particular topic partition will be appended in the order they are sent. That is, if a record M1 is sent by the same producer as a record M2, and M1 is sent first, then M1 will have a lower offset than M2 and appear earlier in the log.
+    <li>A consumer instance sees records in the order they are stored in the log.
+    <li>For a topic with replication factor N, we will tolerate up to N-1 server failures without losing any records committed to the log.
+  </ul>
+  <p>
+  More details on these guarantees are given in the design section of the documentation.
+  </p>
+  <h4><a id="kafka_mq" href="#kafka_mq">Kafka as a Messaging System</a></h4>
+  <p>
+  How does Kafka's notion of streams compare to a traditional enterprise messaging system?
+  </p>
+  <p>
+  Messaging traditionally has two models: <a href="http://en.wikipedia.org/wiki/Message_queue">queuing</a> and <a href="http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern">publish-subscribe</a>. In a queue, a pool of consumers may read from a server and each record goes to one of them; in publish-subscribe the record is broadcast to all consumers. Each of these two models has a strength and a weakness. The strength of queuing is that it allows you to divide up the processing  [...]
+  </p>
+  <p>
+  The consumer group concept in Kafka generalizes these two concepts. As with a queue the consumer group allows you to divide up processing over a collection of processes (the members of the consumer group). As with publish-subscribe, Kafka allows you to broadcast messages to multiple consumer groups.
+  </p>
+  <p>
+  The advantage of Kafka's model is that every topic has both these properties&mdash;it can scale processing and is also multi-subscriber&mdash;there is no need to choose one or the other.
+  </p>
+  <p>
+  Kafka has stronger ordering guarantees than a traditional messaging system, too.
+  </p>
+  <p>
+  A traditional queue retains records in-order on the server, and if multiple consumers consume from the queue then the server hands out records in the order they are stored. However, although the server hands out records in order, the records are delivered asynchronously to consumers, so they may arrive out of order on different consumers. This effectively means the ordering of the records is lost in the presence of parallel consumption. Messaging systems often work around this by havin [...]
+  </p>
+  <p>
+  Kafka does it better. By having a notion of parallelism&mdash;the partition&mdash;within the topics, Kafka is able to provide both ordering guarantees and load balancing over a pool of consumer processes. This is achieved by assigning the partitions in the topic to the consumers in the consumer group so that each partition is consumed by exactly one consumer in the group. By doing this we ensure that the consumer is the only reader of that partition and consumes the data in order. Sinc [...]
+  </p>
+
+  <h4 id="kafka_storage">Kafka as a Storage System</h4>
+
+  <p>
+  Any message queue that allows publishing messages decoupled from consuming them is effectively acting as a storage system for the in-flight messages. What is different about Kafka is that it is a very good storage system.
+  </p>
+  <p>
+  Data written to Kafka is written to disk and replicated for fault-tolerance. Kafka allows producers to wait on acknowledgement so that a write isn't considered complete until it is fully replicated and guaranteed to persist even if the server written to fails.
+  </p>
+  <p>
+  The disk structures Kafka uses scale well&mdash;Kafka will perform the same whether you have 50 KB or 50 TB of persistent data on the server.
+  </p>
+  <p>
+  As a result of taking storage seriously and allowing the clients to control their read position, you can think of Kafka as a kind of special purpose distributed filesystem dedicated to high-performance, low-latency commit log storage, replication, and propagation.
+  </p>
+  <p>
+  For details about the Kafka's commit log storage and replication design, please read <a href="https://kafka.apache.org/documentation/#design">this</a> page.
+  </p>
+  <h4>Kafka for Stream Processing</h4>
+  <p>
+  It isn't enough to just read, write, and store streams of data, the purpose is to enable real-time processing of streams.
+  </p>
+  <p>
+  In Kafka a stream processor is anything that takes continual streams of  data from input topics, performs some processing on this input, and produces continual streams of data to output topics.
+  </p>
+  <p>
+  For example, a retail application might take in input streams of sales and shipments, and output a stream of reorders and price adjustments computed off this data.
+  </p>
+  <p>
+  It is possible to do simple processing directly using the producer and consumer APIs. However for more complex transformations Kafka provides a fully integrated <a href="/documentation/streams">Streams API</a>. This allows building applications that do non-trivial processing that compute aggregations off of streams or join streams together.
+  </p>
+  <p>
+  This facility helps solve the hard problems this type of application faces: handling out-of-order data, reprocessing input as code changes, performing stateful computations, etc.
+  </p>
+  <p>
+  The streams API builds on the core primitives Kafka provides: it uses the producer and consumer APIs for input, uses Kafka for stateful storage, and uses the same group mechanism for fault tolerance among the stream processor instances.
+  </p>
+  <h4>Putting the Pieces Together</h4>
+  <p>
+  This combination of messaging, storage, and stream processing may seem unusual but it is essential to Kafka's role as a streaming platform.
+  </p>
+  <p>
+  A distributed file system like HDFS allows storing static files for batch processing. Effectively a system like this allows storing and processing <i>historical</i> data from the past.
+  </p>
+  <p>
+  A traditional enterprise messaging system allows processing future messages that will arrive after you subscribe. Applications built in this way process future data as it arrives.
+  </p>
+  <p>
+  Kafka combines both of these capabilities, and the combination is critical both for Kafka usage as a platform for streaming applications as well as for streaming data pipelines.
+  </p>
+  <p>
+  By combining storage and low-latency subscriptions, streaming applications can treat both past and future data the same way. That is a single application can process historical, stored data but rather than ending when it reaches the last record it can keep processing as future data arrives. This is a generalized notion of stream processing that subsumes batch processing as well as message-driven applications.
+  </p>
+  <p>
+  Likewise for streaming data pipelines the combination of subscription to real-time events make it possible to use Kafka for very low-latency pipelines; but the ability to store data reliably make it possible to use it for critical data where the delivery of data must be guaranteed or for integration with offline systems that load data only periodically or may go down for extended periods of time for maintenance. The stream processing facilities make it possible to transform data as it  [...]
+  </p>
+  <p>
+  For more information on the guarantees, APIs, and capabilities Kafka provides see the rest of the <a href="/documentation.html">documentation</a>.
+  </p>
+</script>
+
+<div class="p-introduction"></div>
diff --git a/21/js/templateData.js b/21/js/templateData.js
new file mode 100644
index 0000000..eae0b30
--- /dev/null
+++ b/21/js/templateData.js
@@ -0,0 +1,24 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Define variables for doc templates
+var context={
+    "version": "21",
+    "dotVersion": "2.1",
+    "fullDotVersion": "2.1.0",
+    "scalaVersion": "2.11"
+};
diff --git a/21/migration.html b/21/migration.html
new file mode 100644
index 0000000..08a6271
--- /dev/null
+++ b/21/migration.html
@@ -0,0 +1,34 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--#include virtual="../includes/_header.htm" -->
+<h2><a id="migration" href="#migration">Migrating from 0.7.x to 0.8</a></h2>
+
+0.8 is our first (and hopefully last) release with a non-backwards-compatible wire protocol, ZooKeeper     layout, and on-disk data format. This was a chance for us to clean up a lot of cruft and start fresh. This means performing a no-downtime upgrade is more painful than normal&mdash;you cannot just swap in the new code in-place.
+
+<h3><a id="migration_steps" href="#migration_steps">Migration Steps</a></h3>
+
+<ol>
+    <li>Setup a new cluster running 0.8.
+    <li>Use the 0.7 to 0.8 <a href="tools.html">migration tool</a> to mirror data from the 0.7 cluster into the 0.8 cluster.
+    <li>When the 0.8 cluster is fully caught up, redeploy all data <i>consumers</i> running the 0.8 client and reading from the 0.8 cluster.
+    <li>Finally migrate all 0.7 producers to 0.8 client publishing data to the 0.8 cluster.
+    <li>Decommission the 0.7 cluster.
+    <li>Drink.
+</ol>
+
+<!--#include virtual="../includes/_footer.htm" -->
diff --git a/21/ops.html b/21/ops.html
new file mode 100644
index 0000000..158602b
--- /dev/null
+++ b/21/ops.html
@@ -0,0 +1,1915 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<script id="ops-template" type="text/x-handlebars-template">
+
+  Here is some information on actually running Kafka as a production system based on usage and experience at LinkedIn. Please send us any additional tips you know of.
+
+  <h3><a id="basic_ops" href="#basic_ops">6.1 Basic Kafka Operations</a></h3>
+
+  This section will review the most common operations you will perform on your Kafka cluster. All of the tools reviewed in this section are available under the <code>bin/</code> directory of the Kafka distribution and each tool will print details on all possible commandline options if it is run with no arguments.
+
+  <h4><a id="basic_ops_add_topic" href="#basic_ops_add_topic">Adding and removing topics</a></h4>
+
+  You have the option of either adding topics manually or having them be created automatically when data is first published to a non-existent topic. If topics are auto-created then you may want to tune the default <a href="#topicconfigs">topic configurations</a> used for auto-created topics.
+  <p>
+  Topics are added and modified using the topic tool:
+  <pre class="brush: bash;">
+  &gt; bin/kafka-topics.sh --zookeeper zk_host:port/chroot --create --topic my_topic_name
+        --partitions 20 --replication-factor 3 --config x=y
+  </pre>
+  The replication factor controls how many servers will replicate each message that is written. If you have a replication factor of 3 then up to 2 servers can fail before you will lose access to your data. We recommend you use a replication factor of 2 or 3 so that you can transparently bounce machines without interrupting data consumption.
+  <p>
+  The partition count controls how many logs the topic will be sharded into. There are several impacts of the partition count. First each partition must fit entirely on a single server. So if you have 20 partitions the full data set (and read and write load) will be handled by no more than 20 servers (not counting replicas). Finally the partition count impacts the maximum parallelism of your consumers. This is discussed in greater detail in the <a href="#intro_consumers">concepts section</a>.
+  <p>
+  Each sharded partition log is placed into its own folder under the Kafka log directory. The name of such folders consists of the topic name, appended by a dash (-) and the partition id. Since a typical folder name can not be over 255 characters long, there will be a limitation on the length of topic names. We assume the number of partitions will not ever be above 100,000. Therefore, topic names cannot be longer than 249 characters. This leaves just enough room in the folder name for a  [...]
+  <p>
+  The configurations added on the command line override the default settings the server has for things like the length of time data should be retained. The complete set of per-topic configurations is documented <a href="#topicconfigs">here</a>.
+
+  <h4><a id="basic_ops_modify_topic" href="#basic_ops_modify_topic">Modifying topics</a></h4>
+
+  You can change the configuration or partitioning of a topic using the same topic tool.
+  <p>
+  To add partitions you can do
+  <pre class="brush: bash;">
+  &gt; bin/kafka-topics.sh --zookeeper zk_host:port/chroot --alter --topic my_topic_name
+        --partitions 40
+  </pre>
+  Be aware that one use case for partitions is to semantically partition data, and adding partitions doesn't change the partitioning of existing data so this may disturb consumers if they rely on that partition. That is if data is partitioned by <code>hash(key) % number_of_partitions</code> then this partitioning will potentially be shuffled by adding partitions but Kafka will not attempt to automatically redistribute data in any way.
+  <p>
+  To add configs:
+  <pre class="brush: bash;">
+  &gt; bin/kafka-configs.sh --zookeeper zk_host:port/chroot --entity-type topics --entity-name my_topic_name --alter --add-config x=y
+  </pre>
+  To remove a config:
+  <pre class="brush: bash;">
+  &gt; bin/kafka-configs.sh --zookeeper zk_host:port/chroot --entity-type topics --entity-name my_topic_name --alter --delete-config x
+  </pre>
+  And finally deleting a topic:
+  <pre class="brush: bash;">
+  &gt; bin/kafka-topics.sh --zookeeper zk_host:port/chroot --delete --topic my_topic_name
+  </pre>
+  <p>
+  Kafka does not currently support reducing the number of partitions for a topic.
+  <p>
+  Instructions for changing the replication factor of a topic can be found <a href="#basic_ops_increase_replication_factor">here</a>.
+
+  <h4><a id="basic_ops_restarting" href="#basic_ops_restarting">Graceful shutdown</a></h4>
+
+  The Kafka cluster will automatically detect any broker shutdown or failure and elect new leaders for the partitions on that machine. This will occur whether a server fails or it is brought down intentionally for maintenance or configuration changes. For the latter cases Kafka supports a more graceful mechanism for stopping a server than just killing it.
+
+  When a server is stopped gracefully it has two optimizations it will take advantage of:
+  <ol>
+      <li>It will sync all its logs to disk to avoid needing to do any log recovery when it restarts (i.e. validating the checksum for all messages in the tail of the log). Log recovery takes time so this speeds up intentional restarts.
+      <li>It will migrate any partitions the server is the leader for to other replicas prior to shutting down. This will make the leadership transfer faster and minimize the time each partition is unavailable to a few milliseconds.
+  </ol>
+
+  Syncing the logs will happen automatically whenever the server is stopped other than by a hard kill, but the controlled leadership migration requires using a special setting:
+  <pre class="brush: text;">
+      controlled.shutdown.enable=true
+  </pre>
+  Note that controlled shutdown will only succeed if <i>all</i> the partitions hosted on the broker have replicas (i.e. the replication factor is greater than 1 <i>and</i> at least one of these replicas is alive). This is generally what you want since shutting down the last replica would make that topic partition unavailable.
+
+  <h4><a id="basic_ops_leader_balancing" href="#basic_ops_leader_balancing">Balancing leadership</a></h4>
+
+  Whenever a broker stops or crashes leadership for that broker's partitions transfers to other replicas. This means that by default when the broker is restarted it will only be a follower for all its partitions, meaning it will not be used for client reads and writes.
+  <p>
+  To avoid this imbalance, Kafka has a notion of preferred replicas. If the list of replicas for a partition is 1,5,9 then node 1 is preferred as the leader to either node 5 or 9 because it is earlier in the replica list. You can have the Kafka cluster try to restore leadership to the restored replicas by running the command:
+  <pre class="brush: bash;">
+  &gt; bin/kafka-preferred-replica-election.sh --zookeeper zk_host:port/chroot
+  </pre>
+
+  Since running this command can be tedious you can also configure Kafka to do this automatically by setting the following configuration:
+  <pre class="brush: text;">
+      auto.leader.rebalance.enable=true
+  </pre>
+
+  <h4><a id="basic_ops_racks" href="#basic_ops_racks">Balancing Replicas Across Racks</a></h4>
+  The rack awareness feature spreads replicas of the same partition across different racks. This extends the guarantees Kafka provides for broker-failure to cover rack-failure, limiting the risk of data loss should all the brokers on a rack fail at once. The feature can also be applied to other broker groupings such as availability zones in EC2.
+  <p></p>
+  You can specify that a broker belongs to a particular rack by adding a property to the broker config:
+  <pre class="brush: text;">   broker.rack=my-rack-id</pre>
+  When a topic is <a href="#basic_ops_add_topic">created</a>, <a href="#basic_ops_modify_topic">modified</a> or replicas are <a href="#basic_ops_cluster_expansion">redistributed</a>, the rack constraint will be honoured, ensuring replicas span as many racks as they can (a partition will span min(#racks, replication-factor) different racks).
+  <p></p>
+  The algorithm used to assign replicas to brokers ensures that the number of leaders per broker will be constant, regardless of how brokers are distributed across racks. This ensures balanced throughput.
+  <p></p>
+  However if racks are assigned different numbers of brokers, the assignment of replicas will not be even. Racks with fewer brokers will get more replicas, meaning they will use more storage and put more resources into replication. Hence it is sensible to configure an equal number of brokers per rack.
+
+  <h4><a id="basic_ops_mirror_maker" href="#basic_ops_mirror_maker">Mirroring data between clusters</a></h4>
+
+  We refer to the process of replicating data <i>between</i> Kafka clusters "mirroring" to avoid confusion with the replication that happens amongst the nodes in a single cluster. Kafka comes with a tool for mirroring data between Kafka clusters. The tool consumes from a source cluster and produces to a destination cluster.
+
+  A common use case for this kind of mirroring is to provide a replica in another datacenter. This scenario will be discussed in more detail in the next section.
+  <p>
+  You can run many such mirroring processes to increase throughput and for fault-tolerance (if one process dies, the others will take overs the additional load).
+  <p>
+  Data will be read from topics in the source cluster and written to a topic with the same name in the destination cluster. In fact the mirror maker is little more than a Kafka consumer and producer hooked together.
+  <p>
+  The source and destination clusters are completely independent entities: they can have different numbers of partitions and the offsets will not be the same. For this reason the mirror cluster is not really intended as a fault-tolerance mechanism (as the consumer position will be different); for that we recommend using normal in-cluster replication. The mirror maker process will, however, retain and use the message key for partitioning so order is preserved on a per-key basis.
+  <p>
+  Here is an example showing how to mirror a single topic (named <i>my-topic</i>) from an input cluster:
+  <pre class="brush: bash;">
+  &gt; bin/kafka-mirror-maker.sh
+        --consumer.config consumer.properties
+        --producer.config producer.properties --whitelist my-topic
+  </pre>
+  Note that we specify the list of topics with the <code>--whitelist</code> option. This option allows any regular expression using <a href="http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html">Java-style regular expressions</a>. So you could mirror two topics named <i>A</i> and <i>B</i> using <code>--whitelist 'A|B'</code>. Or you could mirror <i>all</i> topics using <code>--whitelist '*'</code>. Make sure to quote any regular expression to ensure the shell doesn't try [...]
+
+  Combining mirroring with the configuration <code>auto.create.topics.enable=true</code> makes it possible to have a replica cluster that will automatically create and replicate all data in a source cluster even as new topics are added.
+
+  <h4><a id="basic_ops_consumer_lag" href="#basic_ops_consumer_lag">Checking consumer position</a></h4>
+  Sometimes it's useful to see the position of your consumers. We have a tool that will show the position of all consumers in a consumer group as well as how far behind the end of the log they are. To run this tool on a consumer group named <i>my-group</i> consuming a topic named <i>my-topic</i> would look like this:
+  <pre class="brush: bash;">
+  &gt; bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group my-group
+
+  TOPIC                          PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG        CONSUMER-ID                                       HOST                           CLIENT-ID
+  my-topic                       0          2               4               2          consumer-1-029af89c-873c-4751-a720-cefd41a669d6   /127.0.0.1                     consumer-1
+  my-topic                       1          2               3               1          consumer-1-029af89c-873c-4751-a720-cefd41a669d6   /127.0.0.1                     consumer-1
+  my-topic                       2          2               3               1          consumer-2-42c1abd4-e3b2-425d-a8bb-e1ea49b29bb2   /127.0.0.1                     consumer-2
+  </pre>
+
+  <h4><a id="basic_ops_consumer_group" href="#basic_ops_consumer_group">Managing Consumer Groups</a></h4>
+
+  With the ConsumerGroupCommand tool, we can list, describe, or delete the consumer groups. The consumer group can be deleted manually, or automatically when the last committed offset for that group expires. Manual deletion works only if the group does not have any active members.
+
+  For example, to list all consumer groups across all topics:
+
+  <pre class="brush: bash;">
+  &gt; bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --list
+
+  test-consumer-group
+  </pre>
+
+  To view offsets, as mentioned earlier, we "describe" the consumer group like this:
+
+  <pre class="brush: bash;">
+  &gt; bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group my-group
+
+  TOPIC           PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG             CONSUMER-ID                                    HOST            CLIENT-ID
+  topic3          0          241019          395308          154289          consumer2-e76ea8c3-5d30-4299-9005-47eb41f3d3c4 /127.0.0.1      consumer2
+  topic2          1          520678          803288          282610          consumer2-e76ea8c3-5d30-4299-9005-47eb41f3d3c4 /127.0.0.1      consumer2
+  topic3          1          241018          398817          157799          consumer2-e76ea8c3-5d30-4299-9005-47eb41f3d3c4 /127.0.0.1      consumer2
+  topic1          0          854144          855809          1665            consumer1-3fc8d6f1-581a-4472-bdf3-3515b4aee8c1 /127.0.0.1      consumer1
+  topic2          0          460537          803290          342753          consumer1-3fc8d6f1-581a-4472-bdf3-3515b4aee8c1 /127.0.0.1      consumer1
+  topic3          2          243655          398812          155157          consumer4-117fe4d3-c6c1-4178-8ee9-eb4a3954bee0 /127.0.0.1      consumer4
+  </pre>
+
+  There are a number of additional "describe" options that can be used to provide more detailed information about a consumer group:
+  <ul>
+    <li>--members: This option provides the list of all active members in the consumer group.
+      <pre class="brush: bash;">
+      &gt; bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group my-group --members
+
+      CONSUMER-ID                                    HOST            CLIENT-ID       #PARTITIONS
+      consumer1-3fc8d6f1-581a-4472-bdf3-3515b4aee8c1 /127.0.0.1      consumer1       2
+      consumer4-117fe4d3-c6c1-4178-8ee9-eb4a3954bee0 /127.0.0.1      consumer4       1
+      consumer2-e76ea8c3-5d30-4299-9005-47eb41f3d3c4 /127.0.0.1      consumer2       3
+      consumer3-ecea43e4-1f01-479f-8349-f9130b75d8ee /127.0.0.1      consumer3       0
+      </pre>
+    </li>
+    <li>--members --verbose: On top of the information reported by the "--members" options above, this option also provides the partitions assigned to each member.
+      <pre class="brush: bash;">
+      &gt; bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group my-group --members --verbose
+
+      CONSUMER-ID                                    HOST            CLIENT-ID       #PARTITIONS     ASSIGNMENT
+      consumer1-3fc8d6f1-581a-4472-bdf3-3515b4aee8c1 /127.0.0.1      consumer1       2               topic1(0), topic2(0)
+      consumer4-117fe4d3-c6c1-4178-8ee9-eb4a3954bee0 /127.0.0.1      consumer4       1               topic3(2)
+      consumer2-e76ea8c3-5d30-4299-9005-47eb41f3d3c4 /127.0.0.1      consumer2       3               topic2(1), topic3(0,1)
+      consumer3-ecea43e4-1f01-479f-8349-f9130b75d8ee /127.0.0.1      consumer3       0               -
+      </pre>
+    </li>
+    <li>--offsets: This is the default describe option and provides the same output as the "--describe" option.</li>
+    <li>--state: This option provides useful group-level information.
+      <pre class="brush: bash;">
+      &gt; bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group my-group --state
+
+      COORDINATOR (ID)          ASSIGNMENT-STRATEGY       STATE                #MEMBERS
+      localhost:9092 (0)        range                     Stable               4
+      </pre>
+    </li>
+  </ul>
+
+  To manually delete one or multiple consumer groups, the "--delete" option can be used:
+  <pre class="brush: bash;">
+  &gt; bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --delete --group my-group --group my-other-group
+
+  Deletion of requested consumer groups ('my-group', 'my-other-group') was successful.
+  </pre>
+
+  <p>
+  To reset offsets of a consumer group, "--reset-offsets" option can be used.
+  This option supports one consumer group at the time. It requires defining following scopes: --all-topics or --topic. One scope must be selected, unless you use '--from-file' scenario. Also, first make sure that the consumer instances are inactive.
+  See  <a href="https://cwiki.apache.org/confluence/display/KAFKA/KIP-122%3A+Add+Reset+Consumer+Group+Offsets+tooling">KIP-122</a> for more details.
+
+  <p>
+  It has 3 execution options:
+  <ul>
+    <li>
+      (default) to display which offsets to reset.
+    </li>
+    <li>
+      --execute : to execute --reset-offsets process.
+    </li>
+    <li>
+      --export : to export the results to a CSV format.
+    </li>
+  </ul>
+
+  <p>
+  --reset-offsets also has following scenarios to choose from (atleast one scenario must be selected):
+  <ul>
+    <li>
+      --to-datetime &lt;String: datetime&gt; : Reset offsets to offsets from datetime. Format: 'YYYY-MM-DDTHH:mm:SS.sss'
+    </li>
+    <li>
+      --to-earliest : Reset offsets to earliest offset.
+    </li>
+    <li>
+      --to-latest : Reset offsets to latest offset.
+    </li>
+    <li>
+      --shift-by &lt;Long: number-of-offsets&gt; : Reset offsets shifting current offset by 'n', where 'n' can be positive or negative.
+    </li>
+    <li>
+      --from-file : Reset offsets to values defined in CSV file.
+    </li>
+    <li>
+      --to-current : Resets offsets to current offset.
+    </li>
+    <li>
+      --by-duration &lt;String: duration&gt; : Reset offsets to offset by duration from current timestamp. Format: 'PnDTnHnMnS'
+    </li>
+    <li>
+      --to-offset : Reset offsets to a specific offset.
+    </li>
+  </ul>
+
+  Please note, that out of range offsets will be adjusted to available offset end. For example, if offset end is at 10 and offset shift request is
+  of 15, then, offset at 10 will actually be selected.
+
+  <p>
+  For example, to reset offsets of a consumer group to the latest offset:
+
+  <pre class="brush: bash;">
+  &gt; bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --reset-offsets --group consumergroup1 --topic topic1 --to-latest
+
+  TOPIC                          PARTITION  NEW-OFFSET
+  topic1                         0          0
+  </pre>
+
+  <p>
+
+  If you are using the old high-level consumer and storing the group metadata in ZooKeeper (i.e. <code>offsets.storage=zookeeper</code>), pass
+  <code>--zookeeper</code> instead of <code>bootstrap-server</code>:
+
+  <pre class="brush: bash;">
+  &gt; bin/kafka-consumer-groups.sh --zookeeper localhost:2181 --list
+  </pre>
+
+  <h4><a id="basic_ops_cluster_expansion" href="#basic_ops_cluster_expansion">Expanding your cluster</a></h4>
+
+  Adding servers to a Kafka cluster is easy, just assign them a unique broker id and start up Kafka on your new servers. However these new servers will not automatically be assigned any data partitions, so unless partitions are moved to them they won't be doing any work until new topics are created. So usually when you add machines to your cluster you will want to migrate some existing data to these machines.
+  <p>
+  The process of migrating data is manually initiated but fully automated. Under the covers what happens is that Kafka will add the new server as a follower of the partition it is migrating and allow it to fully replicate the existing data in that partition. When the new server has fully replicated the contents of this partition and joined the in-sync replica one of the existing replicas will delete their partition's data.
+  <p>
+  The partition reassignment tool can be used to move partitions across brokers. An ideal partition distribution would ensure even data load and partition sizes across all brokers. The partition reassignment tool does not have the capability to automatically study the data distribution in a Kafka cluster and move partitions around to attain an even load distribution. As such, the admin has to figure out which topics or partitions should be moved around.
+  <p>
+  The partition reassignment tool can run in 3 mutually exclusive modes:
+  <ul>
+  <li>--generate: In this mode, given a list of topics and a list of brokers, the tool generates a candidate reassignment to move all partitions of the specified topics to the new brokers. This option merely provides a convenient way to generate a partition reassignment plan given a list of topics and target brokers.</li>
+  <li>--execute: In this mode, the tool kicks off the reassignment of partitions based on the user provided reassignment plan. (using the --reassignment-json-file option). This can either be a custom reassignment plan hand crafted by the admin or provided by using the --generate option</li>
+  <li>--verify: In this mode, the tool verifies the status of the reassignment for all partitions listed during the last --execute. The status can be either of successfully completed, failed or in progress</li>
+  </ul>
+  <h5><a id="basic_ops_automigrate" href="#basic_ops_automigrate">Automatically migrating data to new machines</a></h5>
+  The partition reassignment tool can be used to move some topics off of the current set of brokers to the newly added brokers. This is typically useful while expanding an existing cluster since it is easier to move entire topics to the new set of brokers, than moving one partition at a time. When used to do this, the user should provide a list of topics that should be moved to the new set of brokers and a target list of new brokers. The tool then evenly distributes all partitions for th [...]
+  <p>
+  For instance, the following example will move all partitions for topics foo1,foo2 to the new set of brokers 5,6. At the end of this move, all partitions for topics foo1 and foo2 will <i>only</i> exist on brokers 5,6.
+  <p>
+  Since the tool accepts the input list of topics as a json file, you first need to identify the topics you want to move and create the json file as follows:
+  <pre class="brush: bash;">
+  > cat topics-to-move.json
+  {"topics": [{"topic": "foo1"},
+              {"topic": "foo2"}],
+  "version":1
+  }
+  </pre>
+  Once the json file is ready, use the partition reassignment tool to generate a candidate assignment:
+  <pre class="brush: bash;">
+  > bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --topics-to-move-json-file topics-to-move.json --broker-list "5,6" --generate
+  Current partition replica assignment
+
+  {"version":1,
+  "partitions":[{"topic":"foo1","partition":2,"replicas":[1,2]},
+                {"topic":"foo1","partition":0,"replicas":[3,4]},
+                {"topic":"foo2","partition":2,"replicas":[1,2]},
+                {"topic":"foo2","partition":0,"replicas":[3,4]},
+                {"topic":"foo1","partition":1,"replicas":[2,3]},
+                {"topic":"foo2","partition":1,"replicas":[2,3]}]
+  }
+
+  Proposed partition reassignment configuration
+
+  {"version":1,
+  "partitions":[{"topic":"foo1","partition":2,"replicas":[5,6]},
+                {"topic":"foo1","partition":0,"replicas":[5,6]},
+                {"topic":"foo2","partition":2,"replicas":[5,6]},
+                {"topic":"foo2","partition":0,"replicas":[5,6]},
+                {"topic":"foo1","partition":1,"replicas":[5,6]},
+                {"topic":"foo2","partition":1,"replicas":[5,6]}]
+  }
+  </pre>
+  <p>
+  The tool generates a candidate assignment that will move all partitions from topics foo1,foo2 to brokers 5,6. Note, however, that at this point, the partition movement has not started, it merely tells you the current assignment and the proposed new assignment. The current assignment should be saved in case you want to rollback to it. The new assignment should be saved in a json file (e.g. expand-cluster-reassignment.json) to be input to the tool with the --execute option as follows:
+  <pre class="brush: bash;">
+  > bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --reassignment-json-file expand-cluster-reassignment.json --execute
+  Current partition replica assignment
+
+  {"version":1,
+  "partitions":[{"topic":"foo1","partition":2,"replicas":[1,2]},
+                {"topic":"foo1","partition":0,"replicas":[3,4]},
+                {"topic":"foo2","partition":2,"replicas":[1,2]},
+                {"topic":"foo2","partition":0,"replicas":[3,4]},
+                {"topic":"foo1","partition":1,"replicas":[2,3]},
+                {"topic":"foo2","partition":1,"replicas":[2,3]}]
+  }
+
+  Save this to use as the --reassignment-json-file option during rollback
+  Successfully started reassignment of partitions
+  {"version":1,
+  "partitions":[{"topic":"foo1","partition":2,"replicas":[5,6]},
+                {"topic":"foo1","partition":0,"replicas":[5,6]},
+                {"topic":"foo2","partition":2,"replicas":[5,6]},
+                {"topic":"foo2","partition":0,"replicas":[5,6]},
+                {"topic":"foo1","partition":1,"replicas":[5,6]},
+                {"topic":"foo2","partition":1,"replicas":[5,6]}]
+  }
+  </pre>
+  <p>
+  Finally, the --verify option can be used with the tool to check the status of the partition reassignment. Note that the same expand-cluster-reassignment.json (used with the --execute option) should be used with the --verify option:
+  <pre class="brush: bash;">
+  > bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --reassignment-json-file expand-cluster-reassignment.json --verify
+  Status of partition reassignment:
+  Reassignment of partition [foo1,0] completed successfully
+  Reassignment of partition [foo1,1] is in progress
+  Reassignment of partition [foo1,2] is in progress
+  Reassignment of partition [foo2,0] completed successfully
+  Reassignment of partition [foo2,1] completed successfully
+  Reassignment of partition [foo2,2] completed successfully
+  </pre>
+
+  <h5><a id="basic_ops_partitionassignment" href="#basic_ops_partitionassignment">Custom partition assignment and migration</a></h5>
+  The partition reassignment tool can also be used to selectively move replicas of a partition to a specific set of brokers. When used in this manner, it is assumed that the user knows the reassignment plan and does not require the tool to generate a candidate reassignment, effectively skipping the --generate step and moving straight to the --execute step
+  <p>
+  For instance, the following example moves partition 0 of topic foo1 to brokers 5,6 and partition 1 of topic foo2 to brokers 2,3:
+  <p>
+  The first step is to hand craft the custom reassignment plan in a json file:
+  <pre class="brush: bash;">
+  > cat custom-reassignment.json
+  {"version":1,"partitions":[{"topic":"foo1","partition":0,"replicas":[5,6]},{"topic":"foo2","partition":1,"replicas":[2,3]}]}
+  </pre>
+  Then, use the json file with the --execute option to start the reassignment process:
+  <pre class="brush: bash;">
+  > bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --reassignment-json-file custom-reassignment.json --execute
+  Current partition replica assignment
+
+  {"version":1,
+  "partitions":[{"topic":"foo1","partition":0,"replicas":[1,2]},
+                {"topic":"foo2","partition":1,"replicas":[3,4]}]
+  }
+
+  Save this to use as the --reassignment-json-file option during rollback
+  Successfully started reassignment of partitions
+  {"version":1,
+  "partitions":[{"topic":"foo1","partition":0,"replicas":[5,6]},
+                {"topic":"foo2","partition":1,"replicas":[2,3]}]
+  }
+  </pre>
+  <p>
+  The --verify option can be used with the tool to check the status of the partition reassignment. Note that the same expand-cluster-reassignment.json (used with the --execute option) should be used with the --verify option:
+  <pre class="brush: bash;">
+  > bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --reassignment-json-file custom-reassignment.json --verify
+  Status of partition reassignment:
+  Reassignment of partition [foo1,0] completed successfully
+  Reassignment of partition [foo2,1] completed successfully
+  </pre>
+
+  <h4><a id="basic_ops_decommissioning_brokers" href="#basic_ops_decommissioning_brokers">Decommissioning brokers</a></h4>
+  The partition reassignment tool does not have the ability to automatically generate a reassignment plan for decommissioning brokers yet. As such, the admin has to come up with a reassignment plan to move the replica for all partitions hosted on the broker to be decommissioned, to the rest of the brokers. This can be relatively tedious as the reassignment needs to ensure that all the replicas are not moved from the decommissioned broker to only one other broker. To make this process eff [...]
+
+  <h4><a id="basic_ops_increase_replication_factor" href="#basic_ops_increase_replication_factor">Increasing replication factor</a></h4>
+  Increasing the replication factor of an existing partition is easy. Just specify the extra replicas in the custom reassignment json file and use it with the --execute option to increase the replication factor of the specified partitions.
+  <p>
+  For instance, the following example increases the replication factor of partition 0 of topic foo from 1 to 3. Before increasing the replication factor, the partition's only replica existed on broker 5. As part of increasing the replication factor, we will add more replicas on brokers 6 and 7.
+  <p>
+  The first step is to hand craft the custom reassignment plan in a json file:
+  <pre class="brush: bash;">
+  > cat increase-replication-factor.json
+  {"version":1,
+  "partitions":[{"topic":"foo","partition":0,"replicas":[5,6,7]}]}
+  </pre>
+  Then, use the json file with the --execute option to start the reassignment process:
+  <pre class="brush: bash;">
+  > bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --reassignment-json-file increase-replication-factor.json --execute
+  Current partition replica assignment
+
+  {"version":1,
+  "partitions":[{"topic":"foo","partition":0,"replicas":[5]}]}
+
+  Save this to use as the --reassignment-json-file option during rollback
+  Successfully started reassignment of partitions
+  {"version":1,
+  "partitions":[{"topic":"foo","partition":0,"replicas":[5,6,7]}]}
+  </pre>
+  <p>
+  The --verify option can be used with the tool to check the status of the partition reassignment. Note that the same increase-replication-factor.json (used with the --execute option) should be used with the --verify option:
+  <pre class="brush: bash;">
+  > bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --reassignment-json-file increase-replication-factor.json --verify
+  Status of partition reassignment:
+  Reassignment of partition [foo,0] completed successfully
+  </pre>
+  You can also verify the increase in replication factor with the kafka-topics tool:
+  <pre class="brush: bash;">
+  > bin/kafka-topics.sh --zookeeper localhost:2181 --topic foo --describe
+  Topic:foo	PartitionCount:1	ReplicationFactor:3	Configs:
+    Topic: foo	Partition: 0	Leader: 5	Replicas: 5,6,7	Isr: 5,6,7
+  </pre>
+
+  <h4><a id="rep-throttle" href="#rep-throttle">Limiting Bandwidth Usage during Data Migration</a></h4>
+  Kafka lets you apply a throttle to replication traffic, setting an upper bound on the bandwidth used to move replicas from machine to machine. This is useful when rebalancing a cluster, bootstrapping a new broker or adding or removing brokers, as it limits the impact these data-intensive operations will have on users.
+  <p></p>
+  There are two interfaces that can be used to engage a throttle. The simplest, and safest, is to apply a throttle when invoking the kafka-reassign-partitions.sh, but kafka-configs.sh can also be used to view and alter the throttle values directly.
+  <p></p>
+  So for example, if you were to execute a rebalance, with the below command, it would move partitions at no more than 50MB/s.
+  <pre class="brush: bash;">$ bin/kafka-reassign-partitions.sh --zookeeper myhost:2181--execute --reassignment-json-file bigger-cluster.json —throttle 50000000</pre>
+  When you execute this script you will see the throttle engage:
+  <pre class="brush: bash;">
+  The throttle limit was set to 50000000 B/s
+  Successfully started reassignment of partitions.</pre>
+  <p>Should you wish to alter the throttle, during a rebalance, say to increase the throughput so it completes quicker, you can do this by re-running the execute command passing the same reassignment-json-file:</p>
+  <pre class="brush: bash;">$ bin/kafka-reassign-partitions.sh --zookeeper localhost:2181  --execute --reassignment-json-file bigger-cluster.json --throttle 700000000
+  There is an existing assignment running.
+  The throttle limit was set to 700000000 B/s</pre>
+
+  <p>Once the rebalance completes the administrator can check the status of the rebalance using the --verify option.
+      If the rebalance has completed, the throttle will be removed via the --verify command. It is important that
+      administrators remove the throttle in a timely manner once rebalancing completes by running the command with
+      the --verify option. Failure to do so could cause regular replication traffic to be throttled. </p>
+  <p>When the --verify option is executed, and the reassignment has completed, the script will confirm that the throttle was removed:</p>
+
+  <pre class="brush: bash;">
+  > bin/kafka-reassign-partitions.sh --zookeeper localhost:2181  --verify --reassignment-json-file bigger-cluster.json
+  Status of partition reassignment:
+  Reassignment of partition [my-topic,1] completed successfully
+  Reassignment of partition [mytopic,0] completed successfully
+  Throttle was removed.</pre>
+
+  <p>The administrator can also validate the assigned configs using the kafka-configs.sh. There are two pairs of throttle
+      configuration used to manage the throttling process. The throttle value itself. This is configured, at a broker
+      level, using the dynamic properties: </p>
+
+  <pre class="brush: text;">leader.replication.throttled.rate
+  follower.replication.throttled.rate</pre>
+
+  <p>There is also an enumerated set of throttled replicas: </p>
+
+  <pre class="brush: text;">leader.replication.throttled.replicas
+  follower.replication.throttled.replicas</pre>
+
+  <p>Which are configured per topic. All four config values are automatically assigned by kafka-reassign-partitions.sh
+      (discussed below). </p>
+  <p>To view the throttle limit configuration:</p>
+
+  <pre class="brush: bash;">
+  > bin/kafka-configs.sh --describe --zookeeper localhost:2181 --entity-type brokers
+  Configs for brokers '2' are leader.replication.throttled.rate=700000000,follower.replication.throttled.rate=700000000
+  Configs for brokers '1' are leader.replication.throttled.rate=700000000,follower.replication.throttled.rate=700000000</pre>
+
+  <p>This shows the throttle applied to both leader and follower side of the replication protocol. By default both sides
+      are assigned the same throttled throughput value. </p>
+
+  <p>To view the list of throttled replicas:</p>
+
+  <pre class="brush: bash;">
+  > bin/kafka-configs.sh --describe --zookeeper localhost:2181 --entity-type topics
+  Configs for topic 'my-topic' are leader.replication.throttled.replicas=1:102,0:101,
+      follower.replication.throttled.replicas=1:101,0:102</pre>
+
+  <p>Here we see the leader throttle is applied to partition 1 on broker 102 and partition 0 on broker 101. Likewise the
+      follower throttle is applied to partition 1 on
+      broker 101 and partition 0 on broker 102. </p>
+
+  <p>By default kafka-reassign-partitions.sh will apply the leader throttle to all replicas that exist before the
+      rebalance, any one of which might be leader.
+      It will apply the follower throttle to all move destinations. So if there is a partition with replicas on brokers
+      101,102, being reassigned to 102,103, a leader throttle,
+      for that partition, would be applied to 101,102 and a follower throttle would be applied to 103 only. </p>
+
+
+  <p>If required, you can also use the --alter switch on kafka-configs.sh to alter the throttle configurations manually.
+  </p>
+
+  <h5>Safe usage of throttled replication</h5>
+
+  <p>Some care should be taken when using throttled replication. In particular:</p>
+
+  <p><i>(1) Throttle Removal:</i></p>
+  The throttle should be removed in a timely manner once reassignment completes (by running kafka-reassign-partitions
+  —verify).
+
+  <p><i>(2) Ensuring Progress:</i></p>
+  <p>If the throttle is set too low, in comparison to the incoming write rate, it is possible for replication to not
+      make progress. This occurs when:</p>
+  <pre>max(BytesInPerSec) > throttle</pre>
+  <p>
+      Where BytesInPerSec is the metric that monitors the write throughput of producers into each broker. </p>
+  <p>The administrator can monitor whether replication is making progress, during the rebalance, using the metric:</p>
+
+  <pre>kafka.server:type=FetcherLagMetrics,name=ConsumerLag,clientId=([-.\w]+),topic=([-.\w]+),partition=([0-9]+)</pre>
+
+  <p>The lag should constantly decrease during replication.  If the metric does not decrease the administrator should
+      increase the
+      throttle throughput as described above. </p>
+
+
+  <h4><a id="quotas" href="#quotas">Setting quotas</a></h4>
+  Quotas overrides and defaults may be configured at (user, client-id), user or client-id levels as described <a href="#design_quotas">here</a>.
+  By default, clients receive an unlimited quota.
+
+  It is possible to set custom quotas for each (user, client-id), user or client-id group.
+  <p>
+  Configure custom quota for (user=user1, client-id=clientA):
+  <pre class="brush: bash;">
+  > bin/kafka-configs.sh  --zookeeper localhost:2181 --alter --add-config 'producer_byte_rate=1024,consumer_byte_rate=2048,request_percentage=200' --entity-type users --entity-name user1 --entity-type clients --entity-name clientA
+  Updated config for entity: user-principal 'user1', client-id 'clientA'.
+  </pre>
+
+  Configure custom quota for user=user1:
+  <pre class="brush: bash;">
+  > bin/kafka-configs.sh  --zookeeper localhost:2181 --alter --add-config 'producer_byte_rate=1024,consumer_byte_rate=2048,request_percentage=200' --entity-type users --entity-name user1
+  Updated config for entity: user-principal 'user1'.
+  </pre>
+
+  Configure custom quota for client-id=clientA:
+  <pre class="brush: bash;">
+  > bin/kafka-configs.sh  --zookeeper localhost:2181 --alter --add-config 'producer_byte_rate=1024,consumer_byte_rate=2048,request_percentage=200' --entity-type clients --entity-name clientA
+  Updated config for entity: client-id 'clientA'.
+  </pre>
+
+  It is possible to set default quotas for each (user, client-id), user or client-id group by specifying <i>--entity-default</i> option instead of <i>--entity-name</i>.
+  <p>
+  Configure default client-id quota for user=userA:
+  <pre class="brush: bash;">
+  > bin/kafka-configs.sh  --zookeeper localhost:2181 --alter --add-config 'producer_byte_rate=1024,consumer_byte_rate=2048,request_percentage=200' --entity-type users --entity-name user1 --entity-type clients --entity-default
+  Updated config for entity: user-principal 'user1', default client-id.
+  </pre>
+
+  Configure default quota for user:
+  <pre class="brush: bash;">
+  > bin/kafka-configs.sh  --zookeeper localhost:2181 --alter --add-config 'producer_byte_rate=1024,consumer_byte_rate=2048,request_percentage=200' --entity-type users --entity-default
+  Updated config for entity: default user-principal.
+  </pre>
+
+  Configure default quota for client-id:
+  <pre class="brush: bash;">
+  > bin/kafka-configs.sh  --zookeeper localhost:2181 --alter --add-config 'producer_byte_rate=1024,consumer_byte_rate=2048,request_percentage=200' --entity-type clients --entity-default
+  Updated config for entity: default client-id.
+  </pre>
+
+  Here's how to describe the quota for a given (user, client-id):
+  <pre class="brush: bash;">
+  > bin/kafka-configs.sh  --zookeeper localhost:2181 --describe --entity-type users --entity-name user1 --entity-type clients --entity-name clientA
+  Configs for user-principal 'user1', client-id 'clientA' are producer_byte_rate=1024,consumer_byte_rate=2048,request_percentage=200
+  </pre>
+  Describe quota for a given user:
+  <pre class="brush: bash;">
+  > bin/kafka-configs.sh  --zookeeper localhost:2181 --describe --entity-type users --entity-name user1
+  Configs for user-principal 'user1' are producer_byte_rate=1024,consumer_byte_rate=2048,request_percentage=200
+  </pre>
+  Describe quota for a given client-id:
+  <pre class="brush: bash;">
+  > bin/kafka-configs.sh  --zookeeper localhost:2181 --describe --entity-type clients --entity-name clientA
+  Configs for client-id 'clientA' are producer_byte_rate=1024,consumer_byte_rate=2048,request_percentage=200
+  </pre>
+  If entity name is not specified, all entities of the specified type are described. For example, describe all users:
+  <pre class="brush: bash;">
+  > bin/kafka-configs.sh  --zookeeper localhost:2181 --describe --entity-type users
+  Configs for user-principal 'user1' are producer_byte_rate=1024,consumer_byte_rate=2048,request_percentage=200
+  Configs for default user-principal are producer_byte_rate=1024,consumer_byte_rate=2048,request_percentage=200
+  </pre>
+  Similarly for (user, client):
+  <pre class="brush: bash;">
+  > bin/kafka-configs.sh  --zookeeper localhost:2181 --describe --entity-type users --entity-type clients
+  Configs for user-principal 'user1', default client-id are producer_byte_rate=1024,consumer_byte_rate=2048,request_percentage=200
+  Configs for user-principal 'user1', client-id 'clientA' are producer_byte_rate=1024,consumer_byte_rate=2048,request_percentage=200
+  </pre>
+  <p>
+  It is possible to set default quotas that apply to all client-ids by setting these configs on the brokers. These properties are applied only if quota overrides or defaults are not configured in Zookeeper. By default, each client-id receives an unlimited quota. The following sets the default quota per producer and consumer client-id to 10MB/sec.
+  <pre class="brush: text;">
+    quota.producer.default=10485760
+    quota.consumer.default=10485760
+  </pre>
+  Note that these properties are being deprecated and may be removed in a future release. Defaults configured using kafka-configs.sh take precedence over these properties.
+
+  <h3><a id="datacenters" href="#datacenters">6.2 Datacenters</a></h3>
+
+  Some deployments will need to manage a data pipeline that spans multiple datacenters. Our recommended approach to this is to deploy a local Kafka cluster in each datacenter with application instances in each datacenter interacting only with their local cluster and mirroring between clusters (see the documentation on the <a href="#basic_ops_mirror_maker">mirror maker tool</a> for how to do this).
+  <p>
+  This deployment pattern allows datacenters to act as independent entities and allows us to manage and tune inter-datacenter replication centrally. This allows each facility to stand alone and operate even if the inter-datacenter links are unavailable: when this occurs the mirroring falls behind until the link is restored at which time it catches up.
+  <p>
+  For applications that need a global view of all data you can use mirroring to provide clusters which have aggregate data mirrored from the local clusters in <i>all</i> datacenters. These aggregate clusters are used for reads by applications that require the full data set.
+  <p>
+  This is not the only possible deployment pattern. It is possible to read from or write to a remote Kafka cluster over the WAN, though obviously this will add whatever latency is required to get the cluster.
+  <p>
+  Kafka naturally batches data in both the producer and consumer so it can achieve high-throughput even over a high-latency connection. To allow this though it may be necessary to increase the TCP socket buffer sizes for the producer, consumer, and broker using the <code>socket.send.buffer.bytes</code> and <code>socket.receive.buffer.bytes</code> configurations. The appropriate way to set this is documented <a href="http://en.wikipedia.org/wiki/Bandwidth-delay_product">here</a>.
+  <p>
+  It is generally <i>not</i> advisable to run a <i>single</i> Kafka cluster that spans multiple datacenters over a high-latency link. This will incur very high replication latency both for Kafka writes and ZooKeeper writes, and neither Kafka nor ZooKeeper will remain available in all locations if the network between locations is unavailable.
+
+  <h3><a id="config" href="#config">6.3 Kafka Configuration</a></h3>
+
+  <h4><a id="clientconfig" href="#clientconfig">Important Client Configurations</a></h4>
+
+  The most important producer configurations are:
+  <ul>
+      <li>acks</li>
+      <li>compression</li>
+      <li>batch size</li>
+  </ul>
+  The most important consumer configuration is the fetch size.
+  <p>
+  All configurations are documented in the <a href="#configuration">configuration</a> section.
+  <p>
+  <h4><a id="prodconfig" href="#prodconfig">A Production Server Config</a></h4>
+  Here is an example production server configuration:
+  <pre class="brush: text;">
+  # ZooKeeper
+  zookeeper.connect=[list of ZooKeeper servers]
+
+  # Log configuration
+  num.partitions=8
+  default.replication.factor=3
+  log.dir=[List of directories. Kafka should have its own dedicated disk(s) or SSD(s).]
+
+  # Other configurations
+  broker.id=[An integer. Start with 0 and increment by 1 for each new broker.]
+  listeners=[list of listeners]
+  auto.create.topics.enable=false
+  min.insync.replicas=2
+  queued.max.requests=[number of concurrent requests]
+  </pre>
+
+  Our client configuration varies a fair amount between different use cases.
+
+  <h3><a id="java" href="#java">6.4 Java Version</a></h3>
+
+  From a security perspective, we recommend you use the latest released version of JDK 1.8 as older freely available versions have disclosed security vulnerabilities.
+
+  LinkedIn is currently running JDK 1.8 u5 (looking to upgrade to a newer version) with the G1 collector. LinkedIn's tuning looks like this:
+  <pre class="brush: text;">
+  -Xmx6g -Xms6g -XX:MetaspaceSize=96m -XX:+UseG1GC
+  -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35 -XX:G1HeapRegionSize=16M
+  -XX:MinMetaspaceFreeRatio=50 -XX:MaxMetaspaceFreeRatio=80
+  </pre>
+
+  For reference, here are the stats on one of LinkedIn's busiest clusters (at peak):
+  <ul>
+      <li>60 brokers</li>
+      <li>50k partitions (replication factor 2)</li>
+      <li>800k messages/sec in</li>
+      <li>300 MB/sec inbound, 1 GB/sec+ outbound</li>
+  </ul>
+
+  The tuning looks fairly aggressive, but all of the brokers in that cluster have a 90% GC pause time of about 21ms, and they're doing less than 1 young GC per second.
+
+  <h3><a id="hwandos" href="#hwandos">6.5 Hardware and OS</a></h3>
+  We are using dual quad-core Intel Xeon machines with 24GB of memory.
+  <p>
+  You need sufficient memory to buffer active readers and writers. You can do a back-of-the-envelope estimate of memory needs by assuming you want to be able to buffer for 30 seconds and compute your memory need as write_throughput*30.
+  <p>
+  The disk throughput is important. We have 8x7200 rpm SATA drives. In general disk throughput is the performance bottleneck, and more disks is better. Depending on how you configure flush behavior you may or may not benefit from more expensive disks (if you force flush often then higher RPM SAS drives may be better).
+
+  <h4><a id="os" href="#os">OS</a></h4>
+  Kafka should run well on any unix system and has been tested on Linux and Solaris.
+  <p>
+  We have seen a few issues running on Windows and Windows is not currently a well supported platform though we would be happy to change that.
+  <p>
+  It is unlikely to require much OS-level tuning, but there are three potentially important OS-level configurations:
+  <ul>
+      <li>File descriptor limits: Kafka uses file descriptors for log segments and open connections.  If a broker hosts many partitions, consider that the broker needs at least (number_of_partitions)*(partition_size/segment_size) to track all log segments in addition to the number of connections the broker makes. We recommend at least 100000 allowed file descriptors for the broker processes as a starting point. Note: The mmap() function adds an extra reference to the file associated with [...]
+      <li>Max socket buffer size: can be increased to enable high-performance data transfer between data centers as <a href="http://www.psc.edu/index.php/networking/641-tcp-tune">described here</a>.
+      <li>Maximum number of memory map areas a process may have (aka vm.max_map_count). <a href="http://kernel.org/doc/Documentation/sysctl/vm.txt">See the Linux kernel documentation</a>. You should keep an eye at this OS-level property when considering the maximum number of partitions a broker may have. By default, on a number of Linux systems, the value of vm.max_map_count is somewhere around 65535. Each log segment, allocated per partition, requires a pair of index/timeindex files, an [...]
+  </ul>
+  <p>
+
+  <h4><a id="diskandfs" href="#diskandfs">Disks and Filesystem</a></h4>
+  We recommend using multiple drives to get good throughput and not sharing the same drives used for Kafka data with application logs or other OS filesystem activity to ensure good latency. You can either RAID these drives together into a single volume or format and mount each drive as its own directory. Since Kafka has replication the redundancy provided by RAID can also be provided at the application level. This choice has several tradeoffs.
+  <p>
+  If you configure multiple data directories partitions will be assigned round-robin to data directories. Each partition will be entirely in one of the data directories. If data is not well balanced among partitions this can lead to load imbalance between disks.
+  <p>
+  RAID can potentially do better at balancing load between disks (although it doesn't always seem to) because it balances load at a lower level. The primary downside of RAID is that it is usually a big performance hit for write throughput and reduces the available disk space.
+  <p>
+  Another potential benefit of RAID is the ability to tolerate disk failures. However our experience has been that rebuilding the RAID array is so I/O intensive that it effectively disables the server, so this does not provide much real availability improvement.
+
+  <h4><a id="appvsosflush" href="#appvsosflush">Application vs. OS Flush Management</a></h4>
+  Kafka always immediately writes all data to the filesystem and supports the ability to configure the flush policy that controls when data is forced out of the OS cache and onto disk using the flush. This flush policy can be controlled to force data to disk after a period of time or after a certain number of messages has been written. There are several choices in this configuration.
+  <p>
+  Kafka must eventually call fsync to know that data was flushed. When recovering from a crash for any log segment not known to be fsync'd Kafka will check the integrity of each message by checking its CRC and also rebuild the accompanying offset index file as part of the recovery process executed on startup.
+  <p>
+  Note that durability in Kafka does not require syncing data to disk, as a failed node will always recover from its replicas.
+  <p>
+  We recommend using the default flush settings which disable application fsync entirely. This means relying on the background flush done by the OS and Kafka's own background flush. This provides the best of all worlds for most uses: no knobs to tune, great throughput and latency, and full recovery guarantees. We generally feel that the guarantees provided by replication are stronger than sync to local disk, however the paranoid still may prefer having both and application level fsync po [...]
+  <p>
+  The drawback of using application level flush settings is that it is less efficient in its disk usage pattern (it gives the OS less leeway to re-order writes) and it can introduce latency as fsync in most Linux filesystems blocks writes to the file whereas the background flushing does much more granular page-level locking.
+  <p>
+  In general you don't need to do any low-level tuning of the filesystem, but in the next few sections we will go over some of this in case it is useful.
+
+  <h4><a id="linuxflush" href="#linuxflush">Understanding Linux OS Flush Behavior</a></h4>
+
+  In Linux, data written to the filesystem is maintained in <a href="http://en.wikipedia.org/wiki/Page_cache">pagecache</a> until it must be written out to disk (due to an application-level fsync or the OS's own flush policy). The flushing of data is done by a set of background threads called pdflush (or in post 2.6.32 kernels "flusher threads").
+  <p>
+  Pdflush has a configurable policy that controls how much dirty data can be maintained in cache and for how long before it must be written back to disk.
+  This policy is described <a href="http://web.archive.org/web/20160518040713/http://www.westnet.com/~gsmith/content/linux-pdflush.htm">here</a>.
+  When Pdflush cannot keep up with the rate of data being written it will eventually cause the writing process to block incurring latency in the writes to slow down the accumulation of data.
+  <p>
+  You can see the current state of OS memory usage by doing
+  <pre class="brush: bash;"> &gt; cat /proc/meminfo </pre>
+  The meaning of these values are described in the link above.
+  <p>
+  Using pagecache has several advantages over an in-process cache for storing data that will be written out to disk:
+  <ul>
+    <li>The I/O scheduler will batch together consecutive small writes into bigger physical writes which improves throughput.
+    <li>The I/O scheduler will attempt to re-sequence writes to minimize movement of the disk head which improves throughput.
+    <li>It automatically uses all the free memory on the machine
+  </ul>
+
+  <h4><a id="filesystems" href="#filesystems">Filesystem Selection</a></h4>
+  <p>Kafka uses regular files on disk, and as such it has no hard dependency on a specific filesystem. The two filesystems which have the most usage, however, are EXT4 and XFS. Historically, EXT4 has had more usage, but recent improvements to the XFS filesystem have shown it to have better performance characteristics for Kafka's workload with no compromise in stability.</p>
+  <p>Comparison testing was performed on a cluster with significant message loads, using a variety of filesystem creation and mount options. The primary metric in Kafka that was monitored was the "Request Local Time", indicating the amount of time append operations were taking. XFS resulted in much better local times (160ms vs. 250ms+ for the best EXT4 configuration), as well as lower average wait times. The XFS performance also showed less variability in disk performance.</p>
+  <h5><a id="generalfs" href="#generalfs">General Filesystem Notes</a></h5>
+  For any filesystem used for data directories, on Linux systems, the following options are recommended to be used at mount time:
+  <ul>
+    <li>noatime: This option disables updating of a file's atime (last access time) attribute when the file is read. This can eliminate a significant number of filesystem writes, especially in the case of bootstrapping consumers. Kafka does not rely on the atime attributes at all, so it is safe to disable this.</li>
+  </ul>
+  <h5><a id="xfs" href="#xfs">XFS Notes</a></h5>
+  The XFS filesystem has a significant amount of auto-tuning in place, so it does not require any change in the default settings, either at filesystem creation time or at mount. The only tuning parameters worth considering are:
+  <ul>
+    <li>largeio: This affects the preferred I/O size reported by the stat call. While this can allow for higher performance on larger disk writes, in practice it had minimal or no effect on performance.</li>
+    <li>nobarrier: For underlying devices that have battery-backed cache, this option can provide a little more performance by disabling periodic write flushes. However, if the underlying device is well-behaved, it will report to the filesystem that it does not require flushes, and this option will have no effect.</li>
+  </ul>
+  <h5><a id="ext4" href="#ext4">EXT4 Notes</a></h5>
+  EXT4 is a serviceable choice of filesystem for the Kafka data directories, however getting the most performance out of it will require adjusting several mount options. In addition, these options are generally unsafe in a failure scenario, and will result in much more data loss and corruption. For a single broker failure, this is not much of a concern as the disk can be wiped and the replicas rebuilt from the cluster. In a multiple-failure scenario, such as a power outage, this can mean [...]
+  <ul>
+    <li>data=writeback: Ext4 defaults to data=ordered which puts a strong order on some writes. Kafka does not require this ordering as it does very paranoid data recovery on all unflushed log. This setting removes the ordering constraint and seems to significantly reduce latency.
+    <li>Disabling journaling: Journaling is a tradeoff: it makes reboots faster after server crashes but it introduces a great deal of additional locking which adds variance to write performance. Those who don't care about reboot time and want to reduce a major source of write latency spikes can turn off journaling entirely.
+    <li>commit=num_secs: This tunes the frequency with which ext4 commits to its metadata journal. Setting this to a lower value reduces the loss of unflushed data during a crash. Setting this to a higher value will improve throughput.
+    <li>nobh: This setting controls additional ordering guarantees when using data=writeback mode. This should be safe with Kafka as we do not depend on write ordering and improves throughput and latency.
+    <li>delalloc: Delayed allocation means that the filesystem avoid allocating any blocks until the physical write occurs. This allows ext4 to allocate a large extent instead of smaller pages and helps ensure the data is written sequentially. This feature is great for throughput. It does seem to involve some locking in the filesystem which adds a bit of latency variance.
+  </ul>
+
+  <h3><a id="monitoring" href="#monitoring">6.6 Monitoring</a></h3>
+
+  Kafka uses Yammer Metrics for metrics reporting in the server. The Java clients use Kafka Metrics, a built-in metrics registry that minimizes transitive dependencies pulled into client applications. Both expose metrics via JMX and can be configured to report stats using pluggable stats reporters to hook up to your monitoring system.
+  <p>
+  All Kafka rate metrics have a corresponding cumulative count metric with suffix <code>-total</code>. For example,
+  <code>records-consumed-rate</code> has a corresponding metric named <code>records-consumed-total</code>.
+  <p>
+  The easiest way to see the available metrics is to fire up jconsole and point it at a running kafka client or server; this will allow browsing all metrics with JMX.
+  <p>
+  We do graphing and alerting on the following metrics:
+  <table class="data-table">
+  <tbody><tr>
+        <th>Description</th>
+        <th>Mbean name</th>
+        <th>Normal value</th>
+      </tr>
+      <tr>
+        <td>Message in rate</td>
+        <td>kafka.server:type=BrokerTopicMetrics,name=MessagesInPerSec</td>
+        <td></td>
+      </tr>
+      <tr>
+        <td>Byte in rate from clients</td>
+        <td>kafka.server:type=BrokerTopicMetrics,name=BytesInPerSec</td>
+        <td></td>
+      </tr>
+      <tr>
+        <td>Byte in rate from other brokers</td>
+        <td>kafka.server:type=BrokerTopicMetrics,name=ReplicationBytesInPerSec</td>
+        <td></td>
+      </tr>
+      <tr>
+        <td>Request rate</td>
+        <td>kafka.network:type=RequestMetrics,name=RequestsPerSec,request={Produce|FetchConsumer|FetchFollower}</td>
+        <td></td>
+      </tr>
+      <tr>
+        <td>Error rate</td>
+        <td>kafka.network:type=RequestMetrics,name=ErrorsPerSec,request=([-.\w]+),error=([-.\w]+)</td>
+        <td>Number of errors in responses counted per-request-type, per-error-code. If a response contains
+            multiple errors, all are counted. error=NONE indicates successful responses.</td>
+      </tr>
+      <tr>
+        <td>Request size in bytes</td>
+        <td>kafka.network:type=RequestMetrics,name=RequestBytes,request=([-.\w]+)</td>
+        <td>Size of requests for each request type.</td>
+      </tr>
+      <tr>
+        <td>Temporary memory size in bytes</td>
+        <td>kafka.network:type=RequestMetrics,name=TemporaryMemoryBytes,request={Produce|Fetch}</td>
+        <td>Temporary memory used for message format conversions and decompression.</td>
+      </tr>
+      <tr>
+        <td>Message conversion time</td>
+        <td>kafka.network:type=RequestMetrics,name=MessageConversionsTimeMs,request={Produce|Fetch}</td>
+        <td>Time in milliseconds spent on message format conversions.</td>
+      </tr>
+      <tr>
+        <td>Message conversion rate</td>
+        <td>kafka.server:type=BrokerTopicMetrics,name={Produce|Fetch}MessageConversionsPerSec,topic=([-.\w]+)</td>
+        <td>Number of records which required message format conversion.</td>
+      </tr>
+      <tr>
+        <td>Byte out rate to clients</td>
+        <td>kafka.server:type=BrokerTopicMetrics,name=BytesOutPerSec</td>
+        <td></td>
+      </tr>
+      <tr>
+        <td>Byte out rate to other brokers</td>
+        <td>kafka.server:type=BrokerTopicMetrics,name=ReplicationBytesOutPerSec</td>
+        <td></td>
+      </tr>
+      <tr>
+        <td>Log flush rate and time</td>
+        <td>kafka.log:type=LogFlushStats,name=LogFlushRateAndTimeMs</td>
+        <td></td>
+      </tr>
+      <tr>
+        <td># of under replicated partitions (|ISR| &lt |all replicas|)</td>
+        <td>kafka.server:type=ReplicaManager,name=UnderReplicatedPartitions</td>
+        <td>0</td>
+      </tr>
+      <tr>
+        <td># of under minIsr partitions (|ISR| &lt min.insync.replicas)</td>
+        <td>kafka.server:type=ReplicaManager,name=UnderMinIsrPartitionCount</td>
+        <td>0</td>
+      </tr>
+      <tr>
+        <td># of offline log directories</td>
+        <td>kafka.log:type=LogManager,name=OfflineLogDirectoryCount</td>
+        <td>0</td>
+      </tr>
+      <tr>
+        <td>Is controller active on broker</td>
+        <td>kafka.controller:type=KafkaController,name=ActiveControllerCount</td>
+        <td>only one broker in the cluster should have 1</td>
+      </tr>
+      <tr>
+        <td>Leader election rate</td>
+        <td>kafka.controller:type=ControllerStats,name=LeaderElectionRateAndTimeMs</td>
+        <td>non-zero when there are broker failures</td>
+      </tr>
+      <tr>
+        <td>Unclean leader election rate</td>
+        <td>kafka.controller:type=ControllerStats,name=UncleanLeaderElectionsPerSec</td>
+        <td>0</td>
+      </tr>
+      <tr>
+        <td>Partition counts</td>
+        <td>kafka.server:type=ReplicaManager,name=PartitionCount</td>
+        <td>mostly even across brokers</td>
+      </tr>
+      <tr>
+        <td>Leader replica counts</td>
+        <td>kafka.server:type=ReplicaManager,name=LeaderCount</td>
+        <td>mostly even across brokers</td>
+      </tr>
+      <tr>
+        <td>ISR shrink rate</td>
+        <td>kafka.server:type=ReplicaManager,name=IsrShrinksPerSec</td>
+        <td>If a broker goes down, ISR for some of the partitions will
+    shrink. When that broker is up again, ISR will be expanded
+    once the replicas are fully caught up. Other than that, the
+    expected value for both ISR shrink rate and expansion rate is 0. </td>
+      </tr>
+      <tr>
+        <td>ISR expansion rate</td>
+        <td>kafka.server:type=ReplicaManager,name=IsrExpandsPerSec</td>
+        <td>See above</td>
+      </tr>
+      <tr>
+        <td>Max lag in messages btw follower and leader replicas</td>
+        <td>kafka.server:type=ReplicaFetcherManager,name=MaxLag,clientId=Replica</td>
+        <td>lag should be proportional to the maximum batch size of a produce request.</td>
+      </tr>
+      <tr>
+        <td>Lag in messages per follower replica</td>
+        <td>kafka.server:type=FetcherLagMetrics,name=ConsumerLag,clientId=([-.\w]+),topic=([-.\w]+),partition=([0-9]+)</td>
+        <td>lag should be proportional to the maximum batch size of a produce request.</td>
+      </tr>
+      <tr>
+        <td>Requests waiting in the producer purgatory</td>
+        <td>kafka.server:type=DelayedOperationPurgatory,name=PurgatorySize,delayedOperation=Produce</td>
+        <td>non-zero if ack=-1 is used</td>
+      </tr>
+      <tr>
+        <td>Requests waiting in the fetch purgatory</td>
+        <td>kafka.server:type=DelayedOperationPurgatory,name=PurgatorySize,delayedOperation=Fetch</td>
+        <td>size depends on fetch.wait.max.ms in the consumer</td>
+      </tr>
+      <tr>
+        <td>Request total time</td>
+        <td>kafka.network:type=RequestMetrics,name=TotalTimeMs,request={Produce|FetchConsumer|FetchFollower}</td>
+        <td>broken into queue, local, remote and response send time</td>
+      </tr>
+      <tr>
+        <td>Time the request waits in the request queue</td>
+        <td>kafka.network:type=RequestMetrics,name=RequestQueueTimeMs,request={Produce|FetchConsumer|FetchFollower}</td>
+        <td></td>
+      </tr>
+      <tr>
+        <td>Time the request is processed at the leader</td>
+        <td>kafka.network:type=RequestMetrics,name=LocalTimeMs,request={Produce|FetchConsumer|FetchFollower}</td>
+        <td></td>
+      </tr>
+      <tr>
+        <td>Time the request waits for the follower</td>
+        <td>kafka.network:type=RequestMetrics,name=RemoteTimeMs,request={Produce|FetchConsumer|FetchFollower}</td>
+        <td>non-zero for produce requests when ack=-1</td>
+      </tr>
+      <tr>
+          <td>Time the request waits in the response queue</td>
+          <td>kafka.network:type=RequestMetrics,name=ResponseQueueTimeMs,request={Produce|FetchConsumer|FetchFollower}</td>
+          <td></td>
+      </tr>
+      <tr>
+        <td>Time to send the response</td>
+        <td>kafka.network:type=RequestMetrics,name=ResponseSendTimeMs,request={Produce|FetchConsumer|FetchFollower}</td>
+        <td></td>
+      </tr>
+      <tr>
+        <td>Number of messages the consumer lags behind the producer by. Published by the consumer, not broker.</td>
+        <td>kafka.consumer:type=consumer-fetch-manager-metrics,client-id={client-id} Attribute: records-lag-max</td>
+        <td></td>
+      </tr>
+      <tr>
+        <td>The average fraction of time the network processors are idle</td>
+        <td>kafka.network:type=SocketServer,name=NetworkProcessorAvgIdlePercent</td>
+        <td>between 0 and 1, ideally &gt 0.3</td>
+      </tr>
+      <tr>
+        <td>The average fraction of time the request handler threads are idle</td>
+        <td>kafka.server:type=KafkaRequestHandlerPool,name=RequestHandlerAvgIdlePercent</td>
+        <td>between 0 and 1, ideally &gt 0.3</td>
+      </tr>
+      <tr>
+        <td>Bandwidth quota metrics per (user, client-id), user or client-id</td>
+        <td>kafka.server:type={Produce|Fetch},user=([-.\w]+),client-id=([-.\w]+)</td>
+        <td>Two attributes. throttle-time indicates the amount of time in ms the client was throttled. Ideally = 0.
+            byte-rate indicates the data produce/consume rate of the client in bytes/sec.
+            For (user, client-id) quotas, both user and client-id are specified. If per-client-id quota is applied to the client, user is not specified. If per-user quota is applied, client-id is not specified.</td>
+      </tr>
+      <tr>
+        <td>Request quota metrics per (user, client-id), user or client-id</td>
+        <td>kafka.server:type=Request,user=([-.\w]+),client-id=([-.\w]+)</td>
+        <td>Two attributes. throttle-time indicates the amount of time in ms the client was throttled. Ideally = 0.
+            request-time indicates the percentage of time spent in broker network and I/O threads to process requests from client group.
+            For (user, client-id) quotas, both user and client-id are specified. If per-client-id quota is applied to the client, user is not specified. If per-user quota is applied, client-id is not specified.</td>
+      </tr>
+      <tr>
+        <td>Requests exempt from throttling</td>
+        <td>kafka.server:type=Request</td>
+        <td>exempt-throttle-time indicates the percentage of time spent in broker network and I/O threads to process requests
+            that are exempt from throttling.</td>
+      </tr>
+      <tr>
+        <td>ZooKeeper client request latency</td>
+        <td>kafka.server:type=ZooKeeperClientMetrics,name=ZooKeeperRequestLatencyMs</td>
+        <td>Latency in millseconds for ZooKeeper requests from broker.</td>
+      </tr>
+      <tr>
+        <td>ZooKeeper connection status</td>
+        <td>kafka.server:type=SessionExpireListener,name=SessionState</td>
+        <td>Connection status of broker's ZooKeeper session which may be one of
+            Disconnected|SyncConnected|AuthFailed|ConnectedReadOnly|SaslAuthenticated|Expired.</td>
+      </tr>
+  </tbody></table>
+
+  <h4><a id="selector_monitoring" href="#selector_monitoring">Common monitoring metrics for producer/consumer/connect/streams</a></h4>
+
+  The following metrics are available on producer/consumer/connector/streams instances.  For specific metrics, please see following sections.
+
+  <table class="data-table">
+    <tbody>
+      <tr>
+        <th>Metric/Attribute name</th>
+        <th>Description</th>
+        <th>Mbean name</th>
+      </tr>
+      <tr>
+        <td>connection-close-rate</td>
+        <td>Connections closed per second in the window.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>connection-close-total</td>
+        <td>Total connections closed in the window.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>connection-creation-rate</td>
+        <td>New connections established per second in the window.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>connection-creation-total</td>
+        <td>Total new connections established in the window.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>network-io-rate</td>
+        <td>The average number of network operations (reads or writes) on all connections per second.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>network-io-total</td>
+        <td>The total number of network operations (reads or writes) on all connections.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>outgoing-byte-rate</td>
+        <td>The average number of outgoing bytes sent per second to all servers.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>outgoing-byte-total</td>
+        <td>The total number of outgoing bytes sent to all servers.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>request-rate</td>
+        <td>The average number of requests sent per second.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>request-total</td>
+        <td>The total number of requests sent.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>request-size-avg</td>
+        <td>The average size of all requests in the window.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>request-size-max</td>
+        <td>The maximum size of any request sent in the window.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>incoming-byte-rate</td>
+        <td>Bytes/second read off all sockets.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>incoming-byte-total</td>
+        <td>Total bytes read off all sockets.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>response-rate</td>
+        <td>Responses received per second.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>response-total</td>
+        <td>Total responses received.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>select-rate</td>
+        <td>Number of times the I/O layer checked for new I/O to perform per second.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>select-total</td>
+        <td>Total number of times the I/O layer checked for new I/O to perform.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>io-wait-time-ns-avg</td>
+        <td>The average length of time the I/O thread spent waiting for a socket ready for reads or writes in nanoseconds.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>io-wait-ratio</td>
+        <td>The fraction of time the I/O thread spent waiting.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>io-time-ns-avg</td>
+        <td>The average length of time for I/O per select call in nanoseconds.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>io-ratio</td>
+        <td>The fraction of time the I/O thread spent doing I/O.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>connection-count</td>
+        <td>The current number of active connections.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>successful-authentication-rate</td>
+        <td>Connections per second that were successfully authenticated using SASL or SSL.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>successful-authentication-total</td>
+        <td>Total connections that were successfully authenticated using SASL or SSL.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>failed-authentication-rate</td>
+        <td>Connections per second that failed authentication.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>failed-authentication-total</td>
+        <td>Total connections that failed authentication.</td>
+        <td>kafka.[producer|consumer|connect]:type=[producer|consumer|connect]-metrics,client-id=([-.\w]+)</td>
+      </tr>
+    </tbody>
+  </table>
+
+  <h4><a id="common_node_monitoring" href="#common_node_monitoring">Common Per-broker metrics for producer/consumer/connect/streams</a></h4>
+
+  The following metrics are available on producer/consumer/connector/streams instances.  For specific metrics, please see following sections.
+
+  <table class="data-table">
+    <tbody>
+      <tr>
+        <th>Metric/Attribute name</th>
+        <th>Description</th>
+        <th>Mbean name</th>
+      </tr>
+      <tr>
+        <td>outgoing-byte-rate</td>
+        <td>The average number of outgoing bytes sent per second for a node.</td>
+        <td>kafka.producer:type=[consumer|producer|connect]-node-metrics,client-id=([-.\w]+),node-id=([0-9]+)</td>
+      </tr>
+      <tr>
+        <td>outgoing-byte-total</td>
+        <td>The total number of outgoing bytes sent for a node.</td>
+        <td>kafka.producer:type=[consumer|producer|connect]-node-metrics,client-id=([-.\w]+),node-id=([0-9]+)</td>
+      </tr>
+      <tr>
+        <td>request-rate</td>
+        <td>The average number of requests sent per second for a node.</td>
+        <td>kafka.producer:type=[consumer|producer|connect]-node-metrics,client-id=([-.\w]+),node-id=([0-9]+)</td>
+      </tr>
+      <tr>
+        <td>request-total</td>
+        <td>The total number of requests sent for a node.</td>
+        <td>kafka.producer:type=[consumer|producer|connect]-node-metrics,client-id=([-.\w]+),node-id=([0-9]+)</td>
+      </tr>
+      <tr>
+        <td>request-size-avg</td>
+        <td>The average size of all requests in the window for a node.</td>
+        <td>kafka.producer:type=[consumer|producer|connect]-node-metrics,client-id=([-.\w]+),node-id=([0-9]+)</td>
+      </tr>
+      <tr>
+        <td>request-size-max</td>
+        <td>The maximum size of any request sent in the window for a node.</td>
+        <td>kafka.producer:type=[consumer|producer|connect]-node-metrics,client-id=([-.\w]+),node-id=([0-9]+)</td>
+      </tr>
+      <tr>
+        <td>incoming-byte-rate</td>
+        <td>The average number of bytes received per second for a node.</td>
+        <td>kafka.producer:type=[consumer|producer|connect]-node-metrics,client-id=([-.\w]+),node-id=([0-9]+)</td>
+      </tr>
+      <tr>
+        <td>incoming-byte-total</td>
+        <td>The total number of bytes received for a node.</td>
+        <td>kafka.producer:type=[consumer|producer|connect]-node-metrics,client-id=([-.\w]+),node-id=([0-9]+)</td>
+      </tr>
+      <tr>
+        <td>request-latency-avg</td>
+        <td>The average request latency in ms for a node.</td>
+        <td>kafka.producer:type=[consumer|producer|connect]-node-metrics,client-id=([-.\w]+),node-id=([0-9]+)</td>
+      </tr>
+      <tr>
+        <td>request-latency-max</td>
+        <td>The maximum request latency in ms for a node.</td>
+        <td>kafka.producer:type=[consumer|producer|connect]-node-metrics,client-id=([-.\w]+),node-id=([0-9]+)</td>
+      </tr>
+      <tr>
+        <td>response-rate</td>
+        <td>Responses received per second for a node.</td>
+        <td>kafka.producer:type=[consumer|producer|connect]-node-metrics,client-id=([-.\w]+),node-id=([0-9]+)</td>
+      </tr>
+      <tr>
+        <td>response-total</td>
+        <td>Total responses received for a node.</td>
+        <td>kafka.producer:type=[consumer|producer|connect]-node-metrics,client-id=([-.\w]+),node-id=([0-9]+)</td>
+      </tr>
+    </tbody>
+  </table>
+
+  <h4><a id="producer_monitoring" href="#producer_monitoring">Producer monitoring</a></h4>
+
+  The following metrics are available on producer instances.
+
+  <table class="data-table">
+  <tbody><tr>
+        <th>Metric/Attribute name</th>
+        <th>Description</th>
+        <th>Mbean name</th>
+      </tr>
+        <tr>
+        <td>waiting-threads</td>
+        <td>The number of user threads blocked waiting for buffer memory to enqueue their records.</td>
+        <td>kafka.producer:type=producer-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>buffer-total-bytes</td>
+        <td>The maximum amount of buffer memory the client can use (whether or not it is currently used).</td>
+        <td>kafka.producer:type=producer-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>buffer-available-bytes</td>
+        <td>The total amount of buffer memory that is not being used (either unallocated or in the free list).</td>
+        <td>kafka.producer:type=producer-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>bufferpool-wait-time</td>
+        <td>The fraction of time an appender waits for space allocation.</td>
+        <td>kafka.producer:type=producer-metrics,client-id=([-.\w]+)</td>
+      </tr>
+
+  </tbody></table>
+
+  <h5><a id="producer_sender_monitoring" href="#producer_sender_monitoring">Producer Sender Metrics</a></h5>
+
+  <!--#include virtual="generated/producer_metrics.html" -->
+
+
+  <h4><a id="consumer_monitoring" href="#consumer_monitoring">consumer monitoring</a></h4>
+
+  The following metrics are available on consumer instances.
+
+  <h5><a id="consumer_group_monitoring" href="#consumer_group_monitoring">Consumer Group Metrics</a></h5>
+  <table class="data-table">
+    <tbody>
+      <tr>
+        <th>Metric/Attribute name</th>
+        <th>Description</th>
+        <th>Mbean name</th>
+      </tr>
+      <tr>
+        <td>commit-latency-avg</td>
+        <td>The average time taken for a commit request</td>
+        <td>kafka.consumer:type=consumer-coordinator-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>commit-latency-max</td>
+        <td>The max time taken for a commit request</td>
+        <td>kafka.consumer:type=consumer-coordinator-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>commit-rate</td>
+        <td>The number of commit calls per second</td>
+        <td>kafka.consumer:type=consumer-coordinator-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>commit-total</td>
+        <td>The total number of commit calls</td>
+        <td>kafka.consumer:type=consumer-coordinator-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>assigned-partitions</td>
+        <td>The number of partitions currently assigned to this consumer</td>
+        <td>kafka.consumer:type=consumer-coordinator-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>heartbeat-response-time-max</td>
+        <td>The max time taken to receive a response to a heartbeat request</td>
+        <td>kafka.consumer:type=consumer-coordinator-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>heartbeat-rate</td>
+        <td>The average number of heartbeats per second</td>
+        <td>kafka.consumer:type=consumer-coordinator-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>heartbeat-total</td>
+        <td>The total number of heartbeats</td>
+        <td>kafka.consumer:type=consumer-coordinator-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>join-time-avg</td>
+        <td>The average time taken for a group rejoin</td>
+        <td>kafka.consumer:type=consumer-coordinator-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>join-time-max</td>
+        <td>The max time taken for a group rejoin</td>
+        <td>kafka.consumer:type=consumer-coordinator-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>join-rate</td>
+        <td>The number of group joins per second</td>
+        <td>kafka.consumer:type=consumer-coordinator-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>join-total</td>
+        <td>The total number of group joins</td>
+        <td>kafka.consumer:type=consumer-coordinator-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>sync-time-avg</td>
+        <td>The average time taken for a group sync</td>
+        <td>kafka.consumer:type=consumer-coordinator-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>sync-time-max</td>
+        <td>The max time taken for a group sync</td>
+        <td>kafka.consumer:type=consumer-coordinator-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>sync-rate</td>
+        <td>The number of group syncs per second</td>
+        <td>kafka.consumer:type=consumer-coordinator-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>sync-total</td>
+        <td>The total number of group syncs</td>
+        <td>kafka.consumer:type=consumer-coordinator-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>last-heartbeat-seconds-ago</td>
+        <td>The number of seconds since the last controller heartbeat</td>
+        <td>kafka.consumer:type=consumer-coordinator-metrics,client-id=([-.\w]+)</td>
+      </tr>
+    </tbody>
+  </table>
+
+  <h5><a id="consumer_fetch_monitoring" href="#consumer_fetch_monitoring">Consumer Fetch Metrics</a></h5>
+
+  <!--#include virtual="generated/consumer_metrics.html" -->
+
+  <h4><a id="connect_monitoring" href="#connect_monitoring">Connect Monitoring</a></h4>
+
+  A Connect worker process contains all the producer and consumer metrics as well as metrics specific to Connect.
+  The worker process itself has a number of metrics, while each connector and task have additional metrics.
+
+  <!--#include virtual="generated/connect_metrics.html" -->
+
+  <h4><a id="kafka_streams_monitoring" href="#kafka_streams_monitoring">Streams Monitoring</a></h4>
+
+  A Kafka Streams instance contains all the producer and consumer metrics as well as additional metrics specific to streams.
+  By default Kafka Streams has metrics with two recording levels: debug and info. The debug level records all metrics, while
+  the info level records only the thread-level metrics.
+
+  <p>
+    Note that the metrics have a 3-layer hierarchy. At the top level there are per-thread metrics. Each thread has tasks, with their
+    own metrics. Each task has a number of processor nodes, with their own metrics. Each task also has a number of state stores
+    and record caches, all with their own metrics.
+  </p>
+
+  Use the following configuration option to specify which metrics
+  you want collected:
+
+<pre>metrics.recording.level="info"</pre>
+
+<h5><a id="kafka_streams_thread_monitoring" href="#kafka_streams_thread_monitoring">Thread Metrics</a></h5>
+All the following metrics have a recording level of ``info``:
+<table class="data-table">
+    <tbody>
+      <tr>
+        <th>Metric/Attribute name</th>
+        <th>Description</th>
+        <th>Mbean name</th>
+      </tr>
+       <tr>
+        <td>commit-latency-avg</td>
+        <td>The average execution time in ms for committing, across all running tasks of this thread.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+       </tr>
+        <tr>
+        <td>commit-latency-max</td>
+        <td>The maximum execution time in ms for committing across all running tasks of this thread.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+       </tr>
+       <tr>
+        <td>poll-latency-avg</td>
+        <td>The average execution time in ms for polling, across all running tasks of this thread.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+       </tr>
+        <tr>
+        <td>poll-latency-max</td>
+        <td>The maximum execution time in ms for polling across all running tasks of this thread.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+       </tr>
+       <tr>
+        <td>process-latency-avg</td>
+        <td>The average execution time in ms for processing, across all running tasks of this thread.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+       </tr>
+       <tr>
+        <td>process-latency-max</td>
+        <td>The maximum execution time in ms for processing across all running tasks of this thread.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+       </tr>
+       <tr>
+        <td>punctuate-latency-avg</td>
+        <td>The average execution time in ms for punctuating, across all running tasks of this thread.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+       </tr>
+       <tr>
+        <td>punctuate-latency-max</td>
+        <td>The maximum execution time in ms for punctuating across all running tasks of this thread.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+       </tr>
+       <tr>
+        <td>commit-rate</td>
+        <td>The average number of commits per second.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+       </tr>
+      <tr>
+        <td>commit-total</td>
+        <td>The total number of commit calls across all tasks.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+      </tr>
+       <tr>
+        <td>poll-rate</td>
+        <td>The average number of polls per second.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+       </tr>
+      <tr>
+        <td>poll-total</td>
+        <td>The total number of poll calls across all tasks.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+      </tr>
+       <tr>
+        <td>process-rate</td>
+        <td>The average number of process calls per second.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+       </tr>
+      <tr>
+        <td>process-total</td>
+        <td>The total number of process calls across all tasks.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+      </tr>
+       <tr>
+        <td>punctuate-rate</td>
+        <td>The average number of punctuates per second.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>punctuate-total</td>
+        <td>The total number of punctuate calls across all tasks.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>task-created-rate</td>
+        <td>The average number of newly created tasks per second.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>task-created-total</td>
+        <td>The total number of tasks created.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>task-closed-rate</td>
+        <td>The average number of tasks closed per second.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>task-closed-total</td>
+        <td>The total number of tasks closed.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>skipped-records-rate</td>
+        <td>The average number of skipped records per second.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>skipped-records-total</td>
+        <td>The total number of skipped records.</td>
+        <td>kafka.streams:type=stream-metrics,client-id=([-.\w]+)</td>
+      </tr>
+ </tbody>
+</table>
+
+<h5><a id="kafka_streams_task_monitoring" href="#kafka_streams_task_monitoring">Task Metrics</a></h5>
+All the following metrics have a recording level of ``debug``:
+ <table class="data-table">
+      <tbody>
+      <tr>
+        <th>Metric/Attribute name</th>
+        <th>Description</th>
+        <th>Mbean name</th>
+      </tr>
+      <tr>
+        <td>commit-latency-avg</td>
+        <td>The average commit time in ns for this task. </td>
+        <td>kafka.streams:type=stream-task-metrics,client-id=([-.\w]+),task-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>commit-latency-max</td>
+        <td>The maximum commit time in ns for this task. </td>
+        <td>kafka.streams:type=stream-task-metrics,client-id=([-.\w]+),task-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>commit-rate</td>
+        <td>The average number of commit calls per second. </td>
+        <td>kafka.streams:type=stream-task-metrics,client-id=([-.\w]+),task-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>commit-total</td>
+        <td>The total number of commit calls. </td>
+        <td>kafka.streams:type=stream-task-metrics,client-id=([-.\w]+),task-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>record-lateness-avg</td>
+        <td>The average observed lateness of records.</td>
+        <td>kafka.streams:type=stream-task-metrics,client-id=([-.\w]+),task-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>record-lateness-max</td>
+        <td>The max observed lateness of records.</td>
+        <td>kafka.streams:type=stream-task-metrics,client-id=([-.\w]+),task-id=([-.\w]+)</td>
+      </tr>
+ </tbody>
+</table>
+
+ <h5><a id="kafka_streams_node_monitoring" href="#kafka_streams_node_monitoring">Processor Node Metrics</a></h5>
+ All the following metrics have a recording level of ``debug``:
+ <table class="data-table">
+      <tbody>
+      <tr>
+        <th>Metric/Attribute name</th>
+        <th>Description</th>
+        <th>Mbean name</th>
+      </tr>
+      <tr>
+        <td>process-latency-avg</td>
+        <td>The average process execution time in ns. </td>
+        <td>kafka.streams:type=stream-processor-node-metrics,client-id=([-.\w]+),task-id=([-.\w]+),processor-node-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>process-latency-max</td>
+        <td>The maximum process execution time in ns. </td>
+        <td>kafka.streams:type=stream-processor-node-metrics,client-id=([-.\w]+),task-id=([-.\w]+),processor-node-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>punctuate-latency-avg</td>
+        <td>The average punctuate execution time in ns. </td>
+        <td>kafka.streams:type=stream-processor-node-metrics,client-id=([-.\w]+),task-id=([-.\w]+),processor-node-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>punctuate-latency-max</td>
+        <td>The maximum punctuate execution time in ns. </td>
+        <td>kafka.streams:type=stream-processor-node-metrics,client-id=([-.\w]+),task-id=([-.\w]+),processor-node-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>create-latency-avg</td>
+        <td>The average create execution time in ns. </td>
+        <td>kafka.streams:type=stream-processor-node-metrics,client-id=([-.\w]+),task-id=([-.\w]+),processor-node-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>create-latency-max</td>
+        <td>The maximum create execution time in ns. </td>
+        <td>kafka.streams:type=stream-processor-node-metrics,client-id=([-.\w]+),task-id=([-.\w]+),processor-node-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>destroy-latency-avg</td>
+        <td>The average destroy execution time in ns. </td>
+        <td>kafka.streams:type=stream-processor-node-metrics,client-id=([-.\w]+),task-id=([-.\w]+),processor-node-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>destroy-latency-max</td>
+        <td>The maximum destroy execution time in ns. </td>
+        <td>kafka.streams:type=stream-processor-node-metrics,client-id=([-.\w]+),task-id=([-.\w]+),processor-node-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>process-rate</td>
+        <td>The average number of process operations per second. </td>
+        <td>kafka.streams:type=stream-processor-node-metrics,client-id=([-.\w]+),task-id=([-.\w]+),processor-node-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>process-total</td>
+        <td>The total number of process operations called. </td>
+        <td>kafka.streams:type=stream-processor-node-metrics,client-id=([-.\w]+),task-id=([-.\w]+),processor-node-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>punctuate-rate</td>
+        <td>The average number of punctuate operations per second. </td>
+        <td>kafka.streams:type=stream-processor-node-metrics,client-id=([-.\w]+),task-id=([-.\w]+),processor-node-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>punctuate-total</td>
+        <td>The total number of punctuate operations called. </td>
+        <td>kafka.streams:type=stream-processor-node-metrics,client-id=([-.\w]+),task-id=([-.\w]+),processor-node-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>create-rate</td>
+        <td>The average number of create operations per second. </td>
+        <td>kafka.streams:type=stream-processor-node-metrics,client-id=([-.\w]+),task-id=([-.\w]+),processor-node-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>create-total</td>
+        <td>The total number of create operations called. </td>
+        <td>kafka.streams:type=stream-processor-node-metrics,client-id=([-.\w]+),task-id=([-.\w]+),processor-node-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>destroy-rate</td>
+        <td>The average number of destroy operations per second. </td>
+        <td>kafka.streams:type=stream-processor-node-metrics,client-id=([-.\w]+),task-id=([-.\w]+),processor-node-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>destroy-total</td>
+        <td>The total number of destroy operations called. </td>
+        <td>kafka.streams:type=stream-processor-node-metrics,client-id=([-.\w]+),task-id=([-.\w]+),processor-node-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>forward-rate</td>
+        <td>The average rate of records being forwarded downstream, from source nodes only, per second. </td>
+        <td>kafka.streams:type=stream-processor-node-metrics,client-id=([-.\w]+),task-id=([-.\w]+),processor-node-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>forward-total</td>
+        <td>The total number of of records being forwarded downstream, from source nodes only. </td>
+        <td>kafka.streams:type=stream-processor-node-metrics,client-id=([-.\w]+),task-id=([-.\w]+),processor-node-id=([-.\w]+)</td>
+      </tr>
+ </tbody>
+ </table>
+
+ <h5><a id="kafka_streams_store_monitoring" href="#kafka_streams_store_monitoring">State Store Metrics</a></h5>
+ All the following metrics have a recording level of ``debug``. Note that the ``store-scope`` value is specified in <code>StoreSupplier#metricsScope()</code> for user's customized
+ state stores; for built-in state stores, currently we have <code>in-memory-state</code>, <code>in-memory-lru-state</code>, <code>rocksdb-state</code> (for RocksDB backed key-value store),
+  <code>rocksdb-window-state</code> (for RocksDB backed window store) and <code>rocksdb-session-state</code> (for RocksDB backed session store).
+
+  <table class="data-table">
+      <tbody>
+      <tr>
+        <th>Metric/Attribute name</th>
+        <th>Description</th>
+        <th>Mbean name</th>
+      </tr>
+      <tr>
+        <td>put-latency-avg</td>
+        <td>The average put execution time in ns. </td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>put-latency-max</td>
+        <td>The maximum put execution time in ns. </td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>put-if-absent-latency-avg</td>
+        <td>The average put-if-absent execution time in ns. </td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>put-if-absent-latency-max</td>
+        <td>The maximum put-if-absent execution time in ns. </td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>get-latency-avg</td>
+        <td>The average get execution time in ns. </td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>get-latency-max</td>
+        <td>The maximum get execution time in ns. </td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>delete-latency-avg</td>
+        <td>The average delete execution time in ns. </td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>delete-latency-max</td>
+        <td>The maximum delete execution time in ns. </td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>put-all-latency-avg</td>
+        <td>The average put-all execution time in ns. </td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>put-all-latency-max</td>
+        <td>The maximum put-all execution time in ns. </td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>all-latency-avg</td>
+        <td>The average all operation execution time in ns. </td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>all-latency-max</td>
+        <td>The maximum all operation execution time in ns. </td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>range-latency-avg</td>
+        <td>The average range execution time in ns. </td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>range-latency-max</td>
+        <td>The maximum range execution time in ns. </td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+       <tr>
+        <td>flush-latency-avg</td>
+        <td>The average flush execution time in ns. </td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>flush-latency-max</td>
+        <td>The maximum flush execution time in ns. </td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>restore-latency-avg</td>
+        <td>The average restore execution time in ns. </td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>restore-latency-max</td>
+        <td>The maximum restore execution time in ns. </td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>put-rate</td>
+        <td>The average put rate for this store.</td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>put-total</td>
+        <td>The total number of put calls for this store.</td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>put-if-absent-rate</td>
+        <td>The average put-if-absent rate for this store.</td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>put-if-absent-total</td>
+        <td>The total number of put-if-absent calls for this store.</td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>get-rate</td>
+        <td>The average get rate for this store.</td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>get-total</td>
+        <td>The total number of get calls for this store.</td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>delete-rate</td>
+        <td>The average delete rate for this store.</td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>delete-total</td>
+        <td>The total number of delete calls for this store.</td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>put-all-rate</td>
+        <td>The average put-all rate for this store.</td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>put-all-total</td>
+        <td>The total number of put-all calls for this store.</td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>all-rate</td>
+        <td>The average all operation rate for this store.</td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>all-total</td>
+        <td>The total number of all operation calls for this store.</td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>range-rate</td>
+        <td>The average range rate for this store.</td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>range-total</td>
+        <td>The total number of range calls for this store.</td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>flush-rate</td>
+        <td>The average flush rate for this store.</td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>flush-total</td>
+        <td>The total number of flush calls for this store.</td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>restore-rate</td>
+        <td>The average restore rate for this store.</td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>restore-total</td>
+        <td>The total number of restore calls for this store.</td>
+        <td>kafka.streams:type=stream-[store-scope]-metrics,client-id=([-.\w]+),task-id=([-.\w]+),[store-scope]-id=([-.\w]+)</td>
+      </tr>
+    </tbody>
+ </table>
+
+  <h5><a id="kafka_streams_cache_monitoring" href="#kafka_streams_cache_monitoring">Record Cache Metrics</a></h5>
+  All the following metrics have a recording level of ``debug``:
+
+  <table class="data-table">
+      <tbody>
+      <tr>
+        <th>Metric/Attribute name</th>
+        <th>Description</th>
+        <th>Mbean name</th>
+      </tr>
+      <tr>
+        <td>hitRatio-avg</td>
+        <td>The average cache hit ratio defined as the ratio of cache read hits over the total cache read requests. </td>
+        <td>kafka.streams:type=stream-record-cache-metrics,client-id=([-.\w]+),task-id=([-.\w]+),record-cache-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>hitRatio-min</td>
+        <td>The mininum cache hit ratio. </td>
+        <td>kafka.streams:type=stream-record-cache-metrics,client-id=([-.\w]+),task-id=([-.\w]+),record-cache-id=([-.\w]+)</td>
+      </tr>
+      <tr>
+        <td>hitRatio-max</td>
+        <td>The maximum cache hit ratio. </td>
+        <td>kafka.streams:type=stream-record-cache-metrics,client-id=([-.\w]+),task-id=([-.\w]+),record-cache-id=([-.\w]+)</td>
+      </tr>
+    </tbody>
+ </table>
+
+  <h4><a id="others_monitoring" href="#others_monitoring">Others</a></h4>
+
+  We recommend monitoring GC time and other stats and various server stats such as CPU utilization, I/O service time, etc.
+
+  On the client side, we recommend monitoring the message/byte rate (global and per topic), request rate/size/time, and on the consumer side, max lag in messages among all partitions and min fetch request rate. For a consumer to keep up, max lag needs to be less than a threshold and min fetch rate needs to be larger than 0.
+
+  <h4><a id="basic_ops_audit" href="#basic_ops_audit">Audit</a></h4>
+  The final alerting we do is on the correctness of the data delivery. We audit that every message that is sent is consumed by all consumers and measure the lag for this to occur. For important topics we alert if a certain completeness is not achieved in a certain time period. The details of this are discussed in KAFKA-260.
+
+  <h3><a id="zk" href="#zk">6.7 ZooKeeper</a></h3>
+
+  <h4><a id="zkversion" href="#zkversion">Stable version</a></h4>
+  The current stable branch is 3.4 and the latest release of that branch is 3.4.9.
+
+  <h4><a id="zkops" href="#zkops">Operationalizing ZooKeeper</a></h4>
+  Operationally, we do the following for a healthy ZooKeeper installation:
+  <ul>
+    <li>Redundancy in the physical/hardware/network layout: try not to put them all in the same rack, decent (but don't go nuts) hardware, try to keep redundant power and network paths, etc. A typical ZooKeeper ensemble has 5 or 7 servers, which tolerates 2 and 3 servers down, respectively. If you have a small deployment, then using 3 servers is acceptable, but keep in mind that you'll only be able to tolerate 1 server down in this case. </li>
+    <li>I/O segregation: if you do a lot of write type traffic you'll almost definitely want the transaction logs on a dedicated disk group. Writes to the transaction log are synchronous (but batched for performance), and consequently, concurrent writes can significantly affect performance. ZooKeeper snapshots can be one such a source of concurrent writes, and ideally should be written on a disk group separate from the transaction log. Snapshots are written to disk asynchronously, so it  [...]
+    <li>Application segregation: Unless you really understand the application patterns of other apps that you want to install on the same box, it can be a good idea to run ZooKeeper in isolation (though this can be a balancing act with the capabilities of the hardware).</li>
+    <li>Use care with virtualization: It can work, depending on your cluster layout and read/write patterns and SLAs, but the tiny overheads introduced by the virtualization layer can add up and throw off ZooKeeper, as it can be very time sensitive</li>
+    <li>ZooKeeper configuration: It's java, make sure you give it 'enough' heap space (We usually run them with 3-5G, but that's mostly due to the data set size we have here). Unfortunately we don't have a good formula for it, but keep in mind that allowing for more ZooKeeper state means that snapshots can become large, and large snapshots affect recovery time. In fact, if the snapshot becomes too large (a few gigabytes), then you may need to increase the initLimit parameter to give enou [...]
+    <li>Monitoring: Both JMX and the 4 letter words (4lw) commands are very useful, they do overlap in some cases (and in those cases we prefer the 4 letter commands, they seem more predictable, or at the very least, they work better with the LI monitoring infrastructure)</li>
+    <li>Don't overbuild the cluster: large clusters, especially in a write heavy usage pattern, means a lot of intracluster communication (quorums on the writes and subsequent cluster member updates), but don't underbuild it (and risk swamping the cluster). Having more servers adds to your read capacity.</li>
+  </ul>
+  Overall, we try to keep the ZooKeeper system as small as will handle the load (plus standard growth capacity planning) and as simple as possible. We try not to do anything fancy with the configuration or application layout as compared to the official release as well as keep it as self contained as possible. For these reasons, we tend to skip the OS packaged versions, since it has a tendency to try to put things in the OS standard hierarchy, which can be 'messy', for want of a better wa [...]
+</script>
+
+<div class="p-ops"></div>
diff --git a/21/protocol.html b/21/protocol.html
new file mode 100644
index 0000000..b884992
--- /dev/null
+++ b/21/protocol.html
@@ -0,0 +1,219 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--#include virtual="../includes/_header.htm" -->
+<!--#include virtual="../includes/_top.htm" -->
+<div class="content">
+    <!--#include virtual="../includes/_nav.htm" -->
+    <div class="right">
+        <h1>Kafka protocol guide</h1>
+
+<p>This document covers the wire protocol implemented in Kafka. It is meant to give a readable guide to the protocol that covers the available requests, their binary format, and the proper way to make use of them to implement a client. This document assumes you understand the basic design and terminology described <a href="https://kafka.apache.org/documentation.html#design">here</a></p>
+
+<ul class="toc">
+    <li><a href="#protocol_preliminaries">Preliminaries</a>
+        <ul>
+            <li><a href="#protocol_network">Network</a>
+            <li><a href="#protocol_partitioning">Partitioning and bootstrapping</a>
+            <li><a href="#protocol_partitioning_strategies">Partitioning Strategies</a>
+            <li><a href="#protocol_batching">Batching</a>
+            <li><a href="#protocol_compatibility">Versioning and Compatibility</a>
+        </ul>
+    </li>
+    <li><a href="#protocol_details">The Protocol</a>
+        <ul>
+            <li><a href="#protocol_types">Protocol Primitive Types</a>
+            <li><a href="#protocol_grammar">Notes on reading the request format grammars</a>
+            <li><a href="#protocol_common">Common Request and Response Structure</a>
+            <li><a href="#protocol_recordbatch">Record Batch</a>
+        </ul>
+    </li>
+    <li><a href="#protocol_constants">Constants</a>
+        <ul>
+            <li><a href="#protocol_error_codes">Error Codes</a>
+            <li><a href="#protocol_api_keys">Api Keys</a>
+        </ul>
+    </li>
+    <li><a href="#protocol_messages">The Messages</a></li>
+    <li><a href="#protocol_philosophy">Some Common Philosophical Questions</a></li>
+</ul>
+
+<h4><a id="protocol_preliminaries" href="#protocol_preliminaries">Preliminaries</a></h4>
+
+<h5><a id="protocol_network" href="#protocol_network">Network</a></h5>
+
+<p>Kafka uses a binary protocol over TCP. The protocol defines all APIs as request response message pairs. All messages are size delimited and are made up of the following primitive types.</p>
+
+<p>The client initiates a socket connection and then writes a sequence of request messages and reads back the corresponding response message. No handshake is required on connection or disconnection. TCP is happier if you maintain persistent connections used for many requests to amortize the cost of the TCP handshake, but beyond this penalty connecting is pretty cheap.</p>
+
+<p>The client will likely need to maintain a connection to multiple brokers, as data is partitioned and the clients will need to talk to the server that has their data. However it should not generally be necessary to maintain multiple connections to a single broker from a single client instance (i.e. connection pooling).</p>
+
+<p>The server guarantees that on a single TCP connection, requests will be processed in the order they are sent and responses will return in that order as well. The broker's request processing allows only a single in-flight request per connection in order to guarantee this ordering. Note that clients can (and ideally should) use non-blocking IO to implement request pipelining and achieve higher throughput. i.e., clients can send requests even while awaiting responses for preceding reques [...]
+
+<p>The server has a configurable maximum limit on request size and any request that exceeds this limit will result in the socket being disconnected.</p>
+
+<h5><a id="protocol_partitioning" href="#protocol_partitioning">Partitioning and bootstrapping</a></h5>
+
+<p>Kafka is a partitioned system so not all servers have the complete data set. Instead recall that topics are split into a pre-defined number of partitions, P, and each partition is replicated with some replication factor, N. Topic partitions themselves are just ordered "commit logs" numbered 0, 1, ..., P.</p>
+
+<p>All systems of this nature have the question of how a particular piece of data is assigned to a particular partition. Kafka clients directly control this assignment, the brokers themselves enforce no particular semantics of which messages should be published to a particular partition. Rather, to publish messages the client directly addresses messages to a particular partition, and when fetching messages, fetches from a particular partition. If two clients want to use the same partitio [...]
+
+<p>These requests to publish or fetch data must be sent to the broker that is currently acting as the leader for a given partition. This condition is enforced by the broker, so a request for a particular partition to the wrong broker will result in an the NotLeaderForPartition error code (described below).</p>
+
+<p>How can the client find out which topics exist, what partitions they have, and which brokers currently host those partitions so that it can direct its requests to the right hosts? This information is dynamic, so you can't just configure each client with some static mapping file. Instead all Kafka brokers can answer a metadata request that describes the current state of the cluster: what topics there are, which partitions those topics have, which broker is the leader for those partitio [...]
+
+<p>In other words, the client needs to somehow find one broker and that broker will tell the client about all the other brokers that exist and what partitions they host. This first broker may itself go down so the best practice for a client implementation is to take a list of two or three URLs to bootstrap from. The user can then choose to use a load balancer or just statically configure two or three of their Kafka hosts in the clients.</p>
+
+<p>The client does not need to keep polling to see if the cluster has changed; it can fetch metadata once when it is instantiated cache that metadata until it receives an error indicating that the metadata is out of date. This error can come in two forms: (1) a socket error indicating the client cannot communicate with a particular broker, (2) an error code in the response to a request indicating that this broker no longer hosts the partition for which data was requested.</p>
+<ol>
+    <li>Cycle through a list of "bootstrap" Kafka URLs until we find one we can connect to. Fetch cluster metadata.</li>
+    <li>Process fetch or produce requests, directing them to the appropriate broker based on the topic/partitions they send to or fetch from.</li>
+    <li>If we get an appropriate error, refresh the metadata and try again.</li>
+</ol>
+
+<h5><a id="protocol_partitioning_strategies" href="#protocol_partitioning_strategies">Partitioning Strategies</a></h5>
+
+<p>As mentioned above the assignment of messages to partitions is something the producing client controls. That said, how should this functionality be exposed to the end-user?</p>
+
+<p>Partitioning really serves two purposes in Kafka:</p>
+<ol>
+    <li>It balances data and request load over brokers</li>
+    <li>It serves as a way to divvy up processing among consumer processes while allowing local state and preserving order within the partition. We call this semantic partitioning.</li>
+</ol>
+
+<p>For a given use case you may care about only one of these or both.</p>
+
+<p>To accomplish simple load balancing a simple approach would be for the client to just round robin requests over all brokers. Another alternative, in an environment where there are many more producers than brokers, would be to have each client chose a single partition at random and publish to that. This later strategy will result in far fewer TCP connections.</p>
+
+<p>Semantic partitioning means using some key in the message to assign messages to partitions. For example if you were processing a click message stream you might want to partition the stream by the user id so that all data for a particular user would go to a single consumer. To accomplish this the client can take a key associated with the message and use some hash of this key to choose the partition to which to deliver the message.</p>
+
+<h5><a id="protocol_batching" href="#protocol_batching">Batching</a></h5>
+
+<p>Our APIs encourage batching small things together for efficiency. We have found this is a very significant performance win. Both our API to send messages and our API to fetch messages always work with a sequence of messages not a single message to encourage this. A clever client can make use of this and support an "asynchronous" mode in which it batches together messages sent individually and sends them in larger clumps. We go even further with this and allow the batching across multi [...]
+
+<p>The client implementer can choose to ignore this and send everything one at a time if they like.</p>
+
+<h5><a id="protocol_compatibility" href="#protocol_compatibility">Versioning and Compatibility</a></h5>
+
+<p>The protocol is designed to enable incremental evolution in a backward compatible fashion. Our versioning is on a per API basis, each version consisting of a request and response pair. Each request contains an API key that identifies the API being invoked and a version number that indicates the format of the request and the expected format of the response.</p>
+
+<p>The intention is that clients will support a range of API versions. When communicating with a particular broker, a given client should use the highest API version supported by both and indicate this version in their requests.</p>
+
+<p>The server will reject requests with a version it does not support, and will always respond to the client with exactly the protocol format it expects based on the version it included in its request. The intended upgrade path is that new features would first be rolled out on the server (with the older clients not making use of them) and then as newer clients are deployed these new features would gradually be taken advantage of.</p>
+
+<p>Our goal is primarily to allow API evolution in an environment where downtime is not allowed and clients and servers cannot all be changed at once.</p>
+
+<p>Currently all versions are baselined at 0, as we evolve these APIs we will indicate the format for each version individually.</p>
+
+<h5><a id="api_versions" href="#api_versions">Retrieving Supported API versions</a></h5>
+<p>In order to work against multiple broker versions, clients need to know what versions of various APIs a
+    broker supports. The broker exposes this information since 0.10.0.0 as described in <a href="https://cwiki.apache.org/confluence/display/KAFKA/KIP-35+-+Retrieving+protocol+version">KIP-35</a>.
+    Clients should use the supported API versions information to choose the highest API version supported by both client and broker. If no such version
+    exists, an error should be reported to the user.</p>
+<p>The following sequence may be used by a client to obtain supported API versions from a broker.</p>
+<ol>
+    <li>Client sends <code>ApiVersionsRequest</code> to a broker after connection has been established with the broker. If SSL is enabled,
+        this happens after SSL connection has been established.</li>
+    <li>On receiving <code>ApiVersionsRequest</code>, a broker returns its full list of supported ApiKeys and
+        versions regardless of current authentication state (e.g., before SASL authentication on an SASL listener, do note that no
+        Kafka protocol requests may take place on a SSL listener before the SSL handshake is finished). If this is considered to
+        leak information about the broker version a workaround is to use SSL with client authentication which is performed at an
+        earlier stage of the connection where the <code>ApiVersionRequest</code> is not available. Also, note that broker versions older
+        than 0.10.0.0 do not support this API and will either ignore the request or close connection in response to the request.</li>
+    <li>If multiple versions of an API are supported by broker and client, clients are recommended to use the latest version supported
+        by the broker and itself.</li>
+    <li>Deprecation of a protocol version is done by marking an API version as deprecated in the protocol documentation.</li>
+    <li>Supported API versions obtained from a broker are only valid for the connection on which that information is obtained.
+        In the event of disconnection, the client should obtain the information from the broker again, as the broker might have been
+        upgraded/downgraded in the mean time.</li>
+</ol>
+
+<h5><a id="sasl_handshake" href="#sasl_handshake">SASL Authentication Sequence</a></h5>
+<p>The following sequence is used for SASL authentication:
+<ol>
+  <li>Kafka <code>ApiVersionsRequest</code> may be sent by the client to obtain the version ranges of requests supported by the broker. This is optional.</li>
+  <li>Kafka <code>SaslHandshakeRequest</code> containing the SASL mechanism for authentication is sent by the client. If the requested mechanism is not enabled
+    in the server, the server responds with the list of supported mechanisms and closes the client connection. If the mechanism is enabled
+    in the server, the server sends a successful response and continues with SASL authentication.</li>
+  <li>The actual SASL authentication is now performed. If <code>SaslHandshakeRequest</code> version is v0, a series of SASL client and server tokens corresponding to the mechanism are sent
+     as opaque packets without wrapping the messages with Kafka protocol headers. If <code>SaslHandshakeRequest</code> version is v1, the <code>SaslAuthenticate</code>
+     request/response are used, where the actual SASL tokens are wrapped in the Kafka protocol. The error code in the final message from the broker will indicate if authentication succeeded or failed.</li>
+  <li>If authentication succeeds, subsequent packets are handled as Kafka API requests. Otherwise, the client connection is closed.</li>
+</ol>
+<p>For interoperability with 0.9.0.x clients, the first packet received by the server is handled as a SASL/GSSAPI client token if it is not a valid
+Kafka request. SASL/GSSAPI authentication is performed starting with this packet, skipping the first two steps above.</p>
+
+
+<h4><a id="protocol_details" href="#protocol_details">The Protocol</a></h4>
+
+<h5><a id="protocol_types" href="#protocol_types">Protocol Primitive Types</a></h5>
+
+<p>The protocol is built out of the following primitive types.</p>
+<!--#include virtual="generated/protocol_types.html" -->
+
+<h5><a id="protocol_grammar" href="#protocol_grammar">Notes on reading the request format grammars</a></h5>
+
+<p>The <a href="https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_Form">BNF</a>s below give an exact context free grammar for the request and response binary format. The BNF is intentionally not compact in order to give human-readable name. As always in a BNF a sequence of productions indicates concatenation. When there are multiple possible productions these are separated with '|' and may be enclosed in parenthesis for grouping. The top-level definition is always given first and subsequ [...]
+
+<h5><a id="protocol_common" href="#protocol_common">Common Request and Response Structure</a></h5>
+
+<p>All requests and responses originate from the following grammar which will be incrementally describe through the rest of this document:</p>
+
+<pre>
+RequestOrResponse => Size (RequestMessage | ResponseMessage)
+  Size => int32
+</pre>
+
+<table class="data-table"><tbody>
+<tr><th>Field</th><th>Description</th></tr>
+<tr><td>message_size</td><td>The message_size field gives the size of the subsequent request or response message in bytes. The client can read requests by first reading this 4 byte size as an integer N, and then reading and parsing the subsequent N bytes of the request.</td></tr>
+</table>
+
+<h5><a id="protocol_recordbatch" href="#protocol_recordbatch">Record Batch</a></h5>
+<p>A description of the record batch format can be found <a href="/documentation/#recordbatch">here</a>.</p>
+
+<h4><a id="protocol_constants" href="#protocol_constants">Constants</a></h4>
+
+<h5><a id="protocol_error_codes" href="#protocol_error_codes">Error Codes</a></h5>
+<p>We use numeric codes to indicate what problem occurred on the server. These can be translated by the client into exceptions or whatever the appropriate error handling mechanism in the client language. Here is a table of the error codes currently in use:</p>
+<!--#include virtual="generated/protocol_errors.html" -->
+
+<h5><a id="protocol_api_keys" href="#protocol_api_keys">Api Keys</a></h5>
+<p>The following are the numeric codes that the ApiKey in the request can take for each of the below request types.</p>
+<!--#include virtual="generated/protocol_api_keys.html" -->
+
+<h4><a id="protocol_messages" href="#protocol_messages">The Messages</a></h4>
+
+<p>This section gives details on each of the individual API Messages, their usage, their binary format, and the meaning of their fields.</p>
+<!--#include virtual="generated/protocol_messages.html" -->
+
+<h4><a id="protocol_philosophy" href="#protocol_philosophy">Some Common Philosophical Questions</a></h4>
+
+<p>Some people have asked why we don't use HTTP. There are a number of reasons, the best is that client implementors can make use of some of the more advanced TCP features--the ability to multiplex requests, the ability to simultaneously poll many connections, etc. We have also found HTTP libraries in many languages to be surprisingly shabby.</p>
+
+<p>Others have asked if maybe we shouldn't support many different protocols. Prior experience with this was that it makes it very hard to add and test new features if they have to be ported across many protocol implementations. Our feeling is that most users don't really see multiple protocols as a feature, they just want a good reliable client in the language of their choice.</p>
+
+<p>Another question is why we don't adopt XMPP, STOMP, AMQP or an existing protocol. The answer to this varies by protocol, but in general the problem is that the protocol does determine large parts of the implementation and we couldn't do what we are doing if we didn't have control over the protocol. Our belief is that it is possible to do better than existing messaging systems have in providing a truly distributed messaging system, and to do this we need to build something that works d [...]
+
+<p>A final question is why we don't use a system like Protocol Buffers or Thrift to define our request messages. These packages excel at helping you to managing lots and lots of serialized messages. However we have only a few messages. Support across languages is somewhat spotty (depending on the package). Finally the mapping between binary log format and wire protocol is something we manage somewhat carefully and this would not be possible with these systems. Finally we prefer the style [...]
+
+    <script>
+        // Show selected style on nav item
+        $(function() { $('.b-nav__project').addClass('selected'); });
+    </script>
+
+<!--#include virtual="../includes/_footer.htm" -->
diff --git a/21/quickstart.html b/21/quickstart.html
new file mode 100644
index 0000000..e709cc7
--- /dev/null
+++ b/21/quickstart.html
@@ -0,0 +1,300 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<script><!--#include virtual="js/templateData.js" --></script>
+
+<script id="quickstart-template" type="text/x-handlebars-template">
+<p>
+This tutorial assumes you are starting fresh and have no existing Kafka or ZooKeeper data.
+Since Kafka console scripts are different for Unix-based and Windows platforms, on Windows platforms use <code>bin\windows\</code> instead of <code>bin/</code>, and change the script extension to <code>.bat</code>.
+</p>
+
+<h4><a id="quickstart_download" href="#quickstart_download">Step 1: Download the code</a></h4>
+
+<a href="https://www.apache.org/dyn/closer.cgi?path=/kafka/{{fullDotVersion}}/kafka_2.11-{{fullDotVersion}}.tgz" title="Kafka downloads">Download</a> the {{fullDotVersion}} release and un-tar it.
+
+<pre class="brush: bash;">
+&gt; tar -xzf kafka_2.11-{{fullDotVersion}}.tgz
+&gt; cd kafka_2.11-{{fullDotVersion}}
+</pre>
+
+<h4><a id="quickstart_startserver" href="#quickstart_startserver">Step 2: Start the server</a></h4>
+
+<p>
+Kafka uses <a href="https://zookeeper.apache.org/">ZooKeeper</a> so you need to first start a ZooKeeper server if you don't already have one. You can use the convenience script packaged with kafka to get a quick-and-dirty single-node ZooKeeper instance.
+</p>
+
+<pre class="brush: bash;">
+&gt; bin/zookeeper-server-start.sh config/zookeeper.properties
+[2013-04-22 15:01:37,495] INFO Reading configuration from: config/zookeeper.properties (org.apache.zookeeper.server.quorum.QuorumPeerConfig)
+...
+</pre>
+
+<p>Now start the Kafka server:</p>
+<pre class="brush: bash;">
+&gt; bin/kafka-server-start.sh config/server.properties
+[2013-04-22 15:01:47,028] INFO Verifying properties (kafka.utils.VerifiableProperties)
+[2013-04-22 15:01:47,051] INFO Property socket.send.buffer.bytes is overridden to 1048576 (kafka.utils.VerifiableProperties)
+...
+</pre>
+
+<h4><a id="quickstart_createtopic" href="#quickstart_createtopic">Step 3: Create a topic</a></h4>
+
+<p>Let's create a topic named "test" with a single partition and only one replica:</p>
+<pre class="brush: bash;">
+&gt; bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
+</pre>
+
+<p>We can now see that topic if we run the list topic command:</p>
+<pre class="brush: bash;">
+&gt; bin/kafka-topics.sh --list --zookeeper localhost:2181
+test
+</pre>
+<p>Alternatively, instead of manually creating topics you can also configure your brokers to auto-create topics when a non-existent topic is published to.</p>
+
+<h4><a id="quickstart_send" href="#quickstart_send">Step 4: Send some messages</a></h4>
+
+<p>Kafka comes with a command line client that will take input from a file or from standard input and send it out as messages to the Kafka cluster. By default, each line will be sent as a separate message.</p>
+<p>
+Run the producer and then type a few messages into the console to send to the server.</p>
+
+<pre class="brush: bash;">
+&gt; bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test
+This is a message
+This is another message
+</pre>
+
+<h4><a id="quickstart_consume" href="#quickstart_consume">Step 5: Start a consumer</a></h4>
+
+<p>Kafka also has a command line consumer that will dump out messages to standard output.</p>
+
+<pre class="brush: bash;">
+&gt; bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning
+This is a message
+This is another message
+</pre>
+<p>
+If you have each of the above commands running in a different terminal then you should now be able to type messages into the producer terminal and see them appear in the consumer terminal.
+</p>
+<p>
+All of the command line tools have additional options; running the command with no arguments will display usage information documenting them in more detail.
+</p>
+
+<h4><a id="quickstart_multibroker" href="#quickstart_multibroker">Step 6: Setting up a multi-broker cluster</a></h4>
+
+<p>So far we have been running against a single broker, but that's no fun. For Kafka, a single broker is just a cluster of size one, so nothing much changes other than starting a few more broker instances. But just to get feel for it, let's expand our cluster to three nodes (still all on our local machine).</p>
+<p>
+First we make a config file for each of the brokers (on Windows use the <code>copy</code> command instead):
+</p>
+<pre class="brush: bash;">
+&gt; cp config/server.properties config/server-1.properties
+&gt; cp config/server.properties config/server-2.properties
+</pre>
+
+<p>
+Now edit these new files and set the following properties:
+</p>
+<pre class="brush: text;">
+
+config/server-1.properties:
+    broker.id=1
+    listeners=PLAINTEXT://:9093
+    log.dirs=/tmp/kafka-logs-1
+
+config/server-2.properties:
+    broker.id=2
+    listeners=PLAINTEXT://:9094
+    log.dirs=/tmp/kafka-logs-2
+</pre>
+<p>The <code>broker.id</code> property is the unique and permanent name of each node in the cluster. We have to override the port and log directory only because we are running these all on the same machine and we want to keep the brokers from all trying to register on the same port or overwrite each other's data.</p>
+<p>
+We already have Zookeeper and our single node started, so we just need to start the two new nodes:
+</p>
+<pre class="brush: bash;">
+&gt; bin/kafka-server-start.sh config/server-1.properties &amp;
+...
+&gt; bin/kafka-server-start.sh config/server-2.properties &amp;
+...
+</pre>
+
+<p>Now create a new topic with a replication factor of three:</p>
+<pre class="brush: bash;">
+&gt; bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3 --partitions 1 --topic my-replicated-topic
+</pre>
+
+<p>Okay but now that we have a cluster how can we know which broker is doing what? To see that run the "describe topics" command:</p>
+<pre class="brush: bash;">
+&gt; bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic my-replicated-topic
+Topic:my-replicated-topic	PartitionCount:1	ReplicationFactor:3	Configs:
+	Topic: my-replicated-topic	Partition: 0	Leader: 1	Replicas: 1,2,0	Isr: 1,2,0
+</pre>
+<p>Here is an explanation of output. The first line gives a summary of all the partitions, each additional line gives information about one partition. Since we have only one partition for this topic there is only one line.</p>
+<ul>
+  <li>"leader" is the node responsible for all reads and writes for the given partition. Each node will be the leader for a randomly selected portion of the partitions.
+  <li>"replicas" is the list of nodes that replicate the log for this partition regardless of whether they are the leader or even if they are currently alive.
+  <li>"isr" is the set of "in-sync" replicas. This is the subset of the replicas list that is currently alive and caught-up to the leader.
+</ul>
+<p>Note that in my example node 1 is the leader for the only partition of the topic.</p>
+<p>
+We can run the same command on the original topic we created to see where it is:
+</p>
+<pre class="brush: bash;">
+&gt; bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic test
+Topic:test	PartitionCount:1	ReplicationFactor:1	Configs:
+	Topic: test	Partition: 0	Leader: 0	Replicas: 0	Isr: 0
+</pre>
+<p>So there is no surprise there&mdash;the original topic has no replicas and is on server 0, the only server in our cluster when we created it.</p>
+<p>
+Let's publish a few messages to our new topic:
+</p>
+<pre class="brush: bash;">
+&gt; bin/kafka-console-producer.sh --broker-list localhost:9092 --topic my-replicated-topic
+...
+my test message 1
+my test message 2
+^C
+</pre>
+<p>Now let's consume these messages:</p>
+<pre class="brush: bash;">
+&gt; bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --from-beginning --topic my-replicated-topic
+...
+my test message 1
+my test message 2
+^C
+</pre>
+
+<p>Now let's test out fault-tolerance. Broker 1 was acting as the leader so let's kill it:</p>
+<pre class="brush: bash;">
+&gt; ps aux | grep server-1.properties
+7564 ttys002    0:15.91 /System/Library/Frameworks/JavaVM.framework/Versions/1.8/Home/bin/java...
+&gt; kill -9 7564
+</pre>
+
+On Windows use:
+<pre class="brush: bash;">
+&gt; wmic process where "caption = 'java.exe' and commandline like '%server-1.properties%'" get processid
+ProcessId
+6016
+&gt; taskkill /pid 6016 /f
+</pre>
+
+<p>Leadership has switched to one of the slaves and node 1 is no longer in the in-sync replica set:</p>
+
+<pre class="brush: bash;">
+&gt; bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic my-replicated-topic
+Topic:my-replicated-topic	PartitionCount:1	ReplicationFactor:3	Configs:
+	Topic: my-replicated-topic	Partition: 0	Leader: 2	Replicas: 1,2,0	Isr: 2,0
+</pre>
+<p>But the messages are still available for consumption even though the leader that took the writes originally is down:</p>
+<pre class="brush: bash;">
+&gt; bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --from-beginning --topic my-replicated-topic
+...
+my test message 1
+my test message 2
+^C
+</pre>
+
+
+<h4><a id="quickstart_kafkaconnect" href="#quickstart_kafkaconnect">Step 7: Use Kafka Connect to import/export data</a></h4>
+
+<p>Writing data from the console and writing it back to the console is a convenient place to start, but you'll probably want
+to use data from other sources or export data from Kafka to other systems. For many systems, instead of writing custom
+integration code you can use Kafka Connect to import or export data.</p>
+
+<p>Kafka Connect is a tool included with Kafka that imports and exports data to Kafka. It is an extensible tool that runs
+<i>connectors</i>, which implement the custom logic for interacting with an external system. In this quickstart we'll see
+how to run Kafka Connect with simple connectors that import data from a file to a Kafka topic and export data from a
+Kafka topic to a file.</p>
+
+<p>First, we'll start by creating some seed data to test with:</p>
+
+<pre class="brush: bash;">
+&gt; echo -e "foo\nbar" > test.txt
+</pre>
+Or on Windows:
+<pre class="brush: bash;">
+&gt; echo foo> test.txt
+&gt; echo bar>> test.txt
+</pre>
+
+<p>Next, we'll start two connectors running in <i>standalone</i> mode, which means they run in a single, local, dedicated
+process. We provide three configuration files as parameters. The first is always the configuration for the Kafka Connect
+process, containing common configuration such as the Kafka brokers to connect to and the serialization format for data.
+The remaining configuration files each specify a connector to create. These files include a unique connector name, the connector
+class to instantiate, and any other configuration required by the connector.</p>
+
+<pre class="brush: bash;">
+&gt; bin/connect-standalone.sh config/connect-standalone.properties config/connect-file-source.properties config/connect-file-sink.properties
+</pre>
+
+<p>
+These sample configuration files, included with Kafka, use the default local cluster configuration you started earlier
+and create two connectors: the first is a source connector that reads lines from an input file and produces each to a Kafka topic
+and the second is a sink connector that reads messages from a Kafka topic and produces each as a line in an output file.
+</p>
+
+<p>
+During startup you'll see a number of log messages, including some indicating that the connectors are being instantiated.
+Once the Kafka Connect process has started, the source connector should start reading lines from <code>test.txt</code> and
+producing them to the topic <code>connect-test</code>, and the sink connector should start reading messages from the topic <code>connect-test</code>
+and write them to the file <code>test.sink.txt</code>. We can verify the data has been delivered through the entire pipeline
+by examining the contents of the output file:
+</p>
+
+
+<pre class="brush: bash;">
+&gt; more test.sink.txt
+foo
+bar
+</pre>
+
+<p>
+Note that the data is being stored in the Kafka topic <code>connect-test</code>, so we can also run a console consumer to see the
+data in the topic (or use custom consumer code to process it):
+</p>
+
+
+<pre class="brush: bash;">
+&gt; bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic connect-test --from-beginning
+{"schema":{"type":"string","optional":false},"payload":"foo"}
+{"schema":{"type":"string","optional":false},"payload":"bar"}
+...
+</pre>
+
+<p>The connectors continue to process data, so we can add data to the file and see it move through the pipeline:</p>
+
+<pre class="brush: bash;">
+&gt; echo Another line>> test.txt
+</pre>
+
+<p>You should see the line appear in the console consumer output and in the sink file.</p>
+
+<h4><a id="quickstart_kafkastreams" href="#quickstart_kafkastreams">Step 8: Use Kafka Streams to process data</a></h4>
+
+<p>
+  Kafka Streams is a client library for building mission-critical real-time applications and microservices,
+  where the input and/or output data is stored in Kafka clusters. Kafka Streams combines the simplicity of
+  writing and deploying standard Java and Scala applications on the client side with the benefits of Kafka's
+  server-side cluster technology to make these applications highly scalable, elastic, fault-tolerant, distributed,
+  and much more. This <a href="/{{version}}/documentation/streams/quickstart">quickstart example</a> will demonstrate how
+  to run a streaming application coded in this library. 
+</p>
+
+
+</script>
+
+<div class="p-quickstart"></div>
diff --git a/21/security.html b/21/security.html
new file mode 100644
index 0000000..b018334
--- /dev/null
+++ b/21/security.html
@@ -0,0 +1,1348 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<script id="security-template" type="text/x-handlebars-template">
+    <h3><a id="security_overview" href="#security_overview">7.1 Security Overview</a></h3>
+    In release 0.9.0.0, the Kafka community added a number of features that, used either separately or together, increases security in a Kafka cluster. The following security measures are currently supported:
+    <ol>
+        <li>Authentication of connections to brokers from clients (producers and consumers), other brokers and tools, using either SSL or SASL. Kafka supports the following SASL mechanisms:
+        <ul>
+            <li>SASL/GSSAPI (Kerberos) - starting at version 0.9.0.0</li>
+            <li>SASL/PLAIN - starting at version 0.10.0.0</li>
+            <li>SASL/SCRAM-SHA-256 and SASL/SCRAM-SHA-512 - starting at version 0.10.2.0</li>
+            <li>SASL/OAUTHBEARER - starting at version 2.0</li>
+        </ul></li>
+        <li>Authentication of connections from brokers to ZooKeeper</li>
+        <li>Encryption of data transferred between brokers and clients, between brokers, or between brokers and tools using SSL (Note that there is a performance degradation when SSL is enabled, the magnitude of which depends on the CPU type and the JVM implementation.)</li>
+        <li>Authorization of read / write operations by clients</li>
+        <li>Authorization is pluggable and integration with external authorization services is supported</li>
+    </ol>
+
+    It's worth noting that security is optional - non-secured clusters are supported, as well as a mix of authenticated, unauthenticated, encrypted and non-encrypted clients.
+
+    The guides below explain how to configure and use the security features in both clients and brokers.
+
+    <h3><a id="security_ssl" href="#security_ssl">7.2 Encryption and Authentication using SSL</a></h3>
+    Apache Kafka allows clients to connect over SSL. By default, SSL is disabled but can be turned on as needed.
+
+    <ol>
+        <li><h4><a id="security_ssl_key" href="#security_ssl_key">Generate SSL key and certificate for each Kafka broker</a></h4>
+            The first step of deploying one or more brokers with the SSL support is to generate the key and the certificate for each machine in the cluster. You can use Java's keytool utility to accomplish this task.
+            We will generate the key into a temporary keystore initially so that we can export and sign it later with CA.
+            <pre class="brush: bash;">
+            keytool -keystore server.keystore.jks -alias localhost -validity {validity} -genkey -keyalg RSA</pre>
+
+            You need to specify two parameters in the above command:
+            <ol>
+                <li>keystore: the keystore file that stores the certificate. The keystore file contains the private key of the certificate; therefore, it needs to be kept safely.</li>
+                <li>validity: the valid time of the certificate in days.</li>
+            </ol>
+            <br>
+
+        <h5><a id="security_confighostname" href="#security_confighostname">Configuring Host Name Verification</a></h5>
+        From Kafka version 2.0.0 onwards, host name verification of servers is enabled by default for client connections
+        as well as inter-broker connections to prevent man-in-the-middle attacks. Server host name verification may be disabled
+        by setting <code>ssl.endpoint.identification.algorithm</code> to an empty string. For example,
+        <pre class="brush: text;">	ssl.endpoint.identification.algorithm=</pre>
+        For dynamically configured broker listeners, hostname verification may be disabled using <code>kafka-configs.sh</code>.
+        For example,
+        <pre class="brush: text;">
+        bin/kafka-configs.sh --bootstrap-server localhost:9093 --entity-type brokers --entity-name 0 --alter --add-config "listener.name.internal.ssl.endpoint.identification.algorithm="
+        </pre>
+
+        For older versions of Kafka, <code>ssl.endpoint.identification.algorithm</code> is not defined by default, so host name
+        verification is not performed. The property should be set to <code>HTTPS</code> to enable host name verification.
+
+        <pre class="brush: text;">	ssl.endpoint.identification.algorithm=HTTPS </pre>
+
+        Host name verification must be enabled to prevent man-in-the-middle attacks if server endpoints are not validated
+        externally.
+
+        <h5><a id="security_configcerthostname" href="#security_configcerthstname">Configuring Host Name In Certificates</a></h5>
+        If host name verification is enabled, clients will verify the server's fully qualified domain name (FQDN) against one of
+        the following two fields:
+        <ol>
+            <li>Common Name (CN)
+            <li>Subject Alternative Name (SAN)
+        </ol>
+        <br>
+        Both fields are valid, RFC-2818 recommends the use of SAN however. SAN is also more flexible, allowing for multiple DNS entries to be declared. Another advantage is that the CN can be set to a more meaningful value for authorization purposes. To add a SAN field  append the following argument <code> -ext SAN=DNS:{FQDN} </code> to the keytool command:
+        <pre class="brush: bash;">
+        keytool -keystore server.keystore.jks -alias localhost -validity {validity} -genkey -keyalg RSA -ext SAN=DNS:{FQDN}
+        </pre>
+        The following command can be run afterwards to verify the contents of the generated certificate:
+        <pre class="brush: bash;">
+        keytool -list -v -keystore server.keystore.jks
+        </pre>
+        </li>
+        <li><h4><a id="security_ssl_ca" href="#security_ssl_ca">Creating your own CA</a></h4>
+            After the first step, each machine in the cluster has a public-private key pair, and a certificate to identify the machine. The certificate, however, is unsigned, which means that an attacker can create such a certificate to pretend to be any machine.<p>
+            Therefore, it is important to prevent forged certificates by signing them for each machine in the cluster. A certificate authority (CA) is responsible for signing certificates. CA works likes a government that issues passports—the government stamps (signs) each passport so that the passport becomes difficult to forge. Other governments verify the stamps to ensure the passport is authentic. Similarly, the CA signs the certificates, and the cryptography guarantees that a signed [...]
+            <pre class="brush: bash;">
+            openssl req -new -x509 -keyout ca-key -out ca-cert -days 365</pre>
+
+            The generated CA is simply a public-private key pair and certificate, and it is intended to sign other certificates.<br>
+
+            The next step is to add the generated CA to the **clients' truststore** so that the clients can trust this CA:
+            <pre class="brush: bash;">
+            keytool -keystore client.truststore.jks -alias CARoot -import -file ca-cert</pre>
+
+            <b>Note:</b> If you configure the Kafka brokers to require client authentication by setting ssl.client.auth to be "requested" or "required" on the <a href="#config_broker">Kafka brokers config</a> then you must provide a truststore for the Kafka brokers as well and it should have all the CA certificates that clients' keys were signed by.
+            <pre class="brush: bash;">
+            keytool -keystore server.truststore.jks -alias CARoot -import -file ca-cert</pre>
+
+            In contrast to the keystore in step 1 that stores each machine's own identity, the truststore of a client stores all the certificates that the client should trust. Importing a certificate into one's truststore also means trusting all certificates that are signed by that certificate. As the analogy above, trusting the government (CA) also means trusting all passports (certificates) that it has issued. This attribute is called the chain of trust, and it is particularly useful w [...]
+
+        <li><h4><a id="security_ssl_signing" href="#security_ssl_signing">Signing the certificate</a></h4>
+            The next step is to sign all certificates generated by step 1 with the CA generated in step 2. First, you need to export the certificate from the keystore:
+            <pre class="brush: bash;">
+            keytool -keystore server.keystore.jks -alias localhost -certreq -file cert-file</pre>
+
+            Then sign it with the CA:
+            <pre class="brush: bash;">
+            openssl x509 -req -CA ca-cert -CAkey ca-key -in cert-file -out cert-signed -days {validity} -CAcreateserial -passin pass:{ca-password}</pre>
+
+            Finally, you need to import both the certificate of the CA and the signed certificate into the keystore:
+            <pre class="brush: bash;">
+            keytool -keystore server.keystore.jks -alias CARoot -import -file ca-cert
+            keytool -keystore server.keystore.jks -alias localhost -import -file cert-signed</pre>
+
+            The definitions of the parameters are the following:
+            <ol>
+                <li>keystore: the location of the keystore</li>
+                <li>ca-cert: the certificate of the CA</li>
+                <li>ca-key: the private key of the CA</li>
+                <li>ca-password: the passphrase of the CA</li>
+                <li>cert-file: the exported, unsigned certificate of the server</li>
+                <li>cert-signed: the signed certificate of the server</li>
+            </ol>
+
+            Here is an example of a bash script with all above steps. Note that one of the commands assumes a password of `test1234`, so either use that password or edit the command before running it.
+                <pre>
+            #!/bin/bash
+            #Step 1
+            keytool -keystore server.keystore.jks -alias localhost -validity 365 -keyalg RSA -genkey
+            #Step 2
+            openssl req -new -x509 -keyout ca-key -out ca-cert -days 365
+            keytool -keystore server.truststore.jks -alias CARoot -import -file ca-cert
+            keytool -keystore client.truststore.jks -alias CARoot -import -file ca-cert
+            #Step 3
+            keytool -keystore server.keystore.jks -alias localhost -certreq -file cert-file
+            openssl x509 -req -CA ca-cert -CAkey ca-key -in cert-file -out cert-signed -days 365 -CAcreateserial -passin pass:test1234
+            keytool -keystore server.keystore.jks -alias CARoot -import -file ca-cert
+            keytool -keystore server.keystore.jks -alias localhost -import -file cert-signed</pre></li>
+        <li><h4><a id="security_configbroker" href="#security_configbroker">Configuring Kafka Brokers</a></h4>
+            Kafka Brokers support listening for connections on multiple ports.
+            We need to configure the following property in server.properties, which must have one or more comma-separated values:
+            <pre>listeners</pre>
+
+            If SSL is not enabled for inter-broker communication (see below for how to enable it), both PLAINTEXT and SSL ports will be necessary.
+            <pre class="brush: text;">
+            listeners=PLAINTEXT://host.name:port,SSL://host.name:port</pre>
+
+            Following SSL configs are needed on the broker side
+            <pre class="brush: text;">
+            ssl.keystore.location=/var/private/ssl/server.keystore.jks
+            ssl.keystore.password=test1234
+            ssl.key.password=test1234
+            ssl.truststore.location=/var/private/ssl/server.truststore.jks
+            ssl.truststore.password=test1234</pre>
+
+            Note: ssl.truststore.password is technically optional but highly recommended. If a password is not set access to the truststore is still available, but integrity checking is disabled.
+
+            Optional settings that are worth considering:
+            <ol>
+                <li>ssl.client.auth=none ("required" => client authentication is required, "requested" => client authentication is requested and client without certs can still connect. The usage of "requested" is discouraged as it provides a false sense of security and misconfigured clients will still connect successfully.)</li>
+                <li>ssl.cipher.suites (Optional). A cipher suite is a named combination of authentication, encryption, MAC and key exchange algorithm used to negotiate the security settings for a network connection using TLS or SSL network protocol. (Default is an empty list)</li>
+                <li>ssl.enabled.protocols=TLSv1.2,TLSv1.1,TLSv1 (list out the SSL protocols that you are going to accept from clients. Do note that SSL is deprecated in favor of TLS and using SSL in production is not recommended)</li>
+                <li>ssl.keystore.type=JKS</li>
+                <li>ssl.truststore.type=JKS</li>
+                <li>ssl.secure.random.implementation=SHA1PRNG</li>
+            </ol>
+            If you want to enable SSL for inter-broker communication, add the following to the server.properties file (it defaults to PLAINTEXT)
+            <pre>
+            security.inter.broker.protocol=SSL</pre>
+
+            <p>
+            Due to import regulations in some countries, the Oracle implementation limits the strength of cryptographic algorithms available by default. If stronger algorithms are needed (for example, AES with 256-bit keys), the <a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html">JCE Unlimited Strength Jurisdiction Policy Files</a> must be obtained and installed in the JDK/JRE. See the
+            <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html">JCA Providers Documentation</a> for more information.
+            </p>
+
+            <p>
+            The JRE/JDK will have a default pseudo-random number generator (PRNG) that is used for cryptography operations, so it is not required to configure the
+            implementation used with the <pre>ssl.secure.random.implementation</pre>. However, there are performance issues with some implementations (notably, the
+            default chosen on Linux systems, <pre>NativePRNG</pre>, utilizes a global lock). In cases where performance of SSL connections becomes an issue,
+            consider explicitly setting the implementation to be used. The <pre>SHA1PRNG</pre> implementation is non-blocking, and has shown very good performance
+            characteristics under heavy load (50 MB/sec of produced messages, plus replication traffic, per-broker).
+            </p>
+
+            Once you start the broker you should be able to see in the server.log
+            <pre>
+            with addresses: PLAINTEXT -> EndPoint(192.168.64.1,9092,PLAINTEXT),SSL -> EndPoint(192.168.64.1,9093,SSL)</pre>
+
+            To check quickly if  the server keystore and truststore are setup properly you can run the following command
+            <pre>openssl s_client -debug -connect localhost:9093 -tls1</pre> (Note: TLSv1 should be listed under ssl.enabled.protocols)<br>
+            In the output of this command you should see server's certificate:
+            <pre>
+            -----BEGIN CERTIFICATE-----
+            {variable sized random bytes}
+            -----END CERTIFICATE-----
+            subject=/C=US/ST=CA/L=Santa Clara/O=org/OU=org/CN=Sriharsha Chintalapani
+            issuer=/C=US/ST=CA/L=Santa Clara/O=org/OU=org/CN=kafka/emailAddress=test@test.com</pre>
+            If the certificate does not show up or if there are any other error messages then your keystore is not setup properly.</li>
+
+        <li><h4><a id="security_configclients" href="#security_configclients">Configuring Kafka Clients</a></h4>
+            SSL is supported only for the new Kafka Producer and Consumer, the older API is not supported. The configs for SSL will be the same for both producer and consumer.<br>
+            If client authentication is not required in the broker, then the following is a minimal configuration example:
+            <pre class="brush: text;">
+            security.protocol=SSL
+            ssl.truststore.location=/var/private/ssl/client.truststore.jks
+            ssl.truststore.password=test1234</pre>
+
+            Note: ssl.truststore.password is technically optional but highly recommended. If a password is not set access to the truststore is still available, but integrity checking is disabled.
+
+            If client authentication is required, then a keystore must be created like in step 1 and the following must also be configured:
+            <pre class="brush: text;">
+            ssl.keystore.location=/var/private/ssl/client.keystore.jks
+            ssl.keystore.password=test1234
+            ssl.key.password=test1234</pre>
+			
+            Other configuration settings that may also be needed depending on our requirements and the broker configuration:
+                <ol>
+                    <li>ssl.provider (Optional). The name of the security provider used for SSL connections. Default value is the default security provider of the JVM.</li>
+                    <li>ssl.cipher.suites (Optional). A cipher suite is a named combination of authentication, encryption, MAC and key exchange algorithm used to negotiate the security settings for a network connection using TLS or SSL network protocol.</li>
+                    <li>ssl.enabled.protocols=TLSv1.2,TLSv1.1,TLSv1. It should list at least one of the protocols configured on the broker side</li>
+                    <li>ssl.truststore.type=JKS</li>
+                    <li>ssl.keystore.type=JKS</li>
+                </ol>
+    <br>
+            Examples using console-producer and console-consumer:
+            <pre class="brush: bash;">
+            kafka-console-producer.sh --broker-list localhost:9093 --topic test --producer.config client-ssl.properties
+            kafka-console-consumer.sh --bootstrap-server localhost:9093 --topic test --consumer.config client-ssl.properties</pre>
+        </li>
+    </ol>
+    <h3><a id="security_sasl" href="#security_sasl">7.3 Authentication using SASL</a></h3>
+
+    <ol>
+    <li><h4><a id="security_sasl_jaasconfig" href="#security_sasl_jaasconfig">JAAS configuration</a></h4>
+    <p>Kafka uses the Java Authentication and Authorization Service
+    (<a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/jaas/JAASRefGuide.html">JAAS</a>)
+    for SASL configuration.</p>
+        <ol>
+        <li><h5><a id="security_jaas_broker"
+            href="#security_jaas_broker">JAAS configuration for Kafka brokers</a></h5>
+
+            <p><tt>KafkaServer</tt> is the section name in the JAAS file used by each
+            KafkaServer/Broker. This section provides SASL configuration options
+            for the broker including any SASL client connections made by the broker
+            for inter-broker communication. If multiple listeners are configured to use
+            SASL, the section name may be prefixed with the listener name in lower-case
+            followed by a period, e.g. <tt>sasl_ssl.KafkaServer</tt>.</p>
+
+            <p><tt>Client</tt> section is used to authenticate a SASL connection with
+            zookeeper. It also allows the brokers to set SASL ACL on zookeeper
+            nodes which locks these nodes down so that only the brokers can
+            modify it. It is necessary to have the same principal name across all
+            brokers. If you want to use a section name other than Client, set the
+            system property <tt>zookeeper.sasl.clientconfig</tt> to the appropriate
+            name (<i>e.g.</i>, <tt>-Dzookeeper.sasl.clientconfig=ZkClient</tt>).</p>
+
+            <p>ZooKeeper uses "zookeeper" as the service name by default. If you
+            want to change this, set the system property
+            <tt>zookeeper.sasl.client.username</tt> to the appropriate name
+            (<i>e.g.</i>, <tt>-Dzookeeper.sasl.client.username=zk</tt>).</p></li>
+
+            <p>Brokers may also configure JAAS using the broker configuration property <code>sasl.jaas.config</code>.
+            The property name must be prefixed with the listener prefix including the SASL mechanism,
+            i.e. <code>listener.name.{listenerName}.{saslMechanism}.sasl.jaas.config</code>. Only one
+            login module may be specified in the config value. If multiple mechanisms are configured on a
+            listener, configs must be provided for each mechanism using the listener and mechanism prefix.
+            For example,
+                    <pre class="brush: text;">
+        listener.name.sasl_ssl.scram-sha-256.sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \
+            username="admin" \
+            password="admin-secret";
+        listener.name.sasl_ssl.plain.sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required \
+            username="admin" \
+            password="admin-secret" \
+            user_admin="admin-secret" \
+            user_alice="alice-secret";</pre>
+
+            If JAAS configuration is defined at different levels, the order of precedence used is:
+            <ul>
+              <li>Broker configuration property <code>listener.name.{listenerName}.{saslMechanism}.sasl.jaas.config</code></li>
+              <li><code>{listenerName}.KafkaServer</code> section of static JAAS configuration</code></li>
+              <li><code>KafkaServer</code> section of static JAAS configuration</code></li>
+            </ul>
+            Note that ZooKeeper JAAS config may only be configured using static JAAS configuration.
+
+            <p>See <a href="#security_sasl_kerberos_brokerconfig">GSSAPI (Kerberos)</a>,
+            <a href="#security_sasl_plain_brokerconfig">PLAIN</a>,
+            <a href="#security_sasl_scram_brokerconfig">SCRAM</a> or
+            <a href="#security_sasl_oauthbearer_brokerconfig">OAUTHBEARER</a> for example broker configurations.</p></li>
+
+
+        <li><h5><a id="security_jaas_client"
+            href="#security_jaas_client">JAAS configuration for Kafka clients</a></h5>
+
+            <p>Clients may configure JAAS using the client configuration property
+            <a href="#security_client_dynamicjaas">sasl.jaas.config</a>
+            or using the <a href="#security_client_staticjaas">static JAAS config file</a>
+            similar to brokers.</p>
+
+            <ol>
+            <li><h6><a id="security_client_dynamicjaas"
+                href="#security_client_dynamicjaas">JAAS configuration using client configuration property</a></h6>
+                <p>Clients may specify JAAS configuration as a producer or consumer property without
+                creating a physical configuration file. This mode also enables different producers
+                and consumers within the same JVM to use different credentials by specifying
+                different properties for each client. If both static JAAS configuration system property
+                <code>java.security.auth.login.config</code> and client property <code>sasl.jaas.config</code>
+                are specified, the client property will be used.</p>
+
+                <p>See <a href="#security_sasl_kerberos_clientconfig">GSSAPI (Kerberos)</a>,
+                <a href="#security_sasl_plain_clientconfig">PLAIN</a>,
+                <a href="#security_sasl_scram_clientconfig">SCRAM</a> or
+                <a href="#security_sasl_oauthbearer_clientconfig">OAUTHBEARER</a> for example configurations.</p></li>
+
+                <li><h6><a id="security_client_staticjaas" href="#security_client_staticjaas">JAAS configuration using static config file</a></h6>
+                To configure SASL authentication on the clients using static JAAS config file:
+                <ol>
+                <li>Add a JAAS config file with a client login section named <tt>KafkaClient</tt>. Configure
+                    a login module in <tt>KafkaClient</tt> for the selected mechanism as described in the examples
+                    for setting up <a href="#security_sasl_kerberos_clientconfig">GSSAPI (Kerberos)</a>,
+                    <a href="#security_sasl_plain_clientconfig">PLAIN</a>,
+                    <a href="#security_sasl_scram_clientconfig">SCRAM</a> or
+                    <a href="#security_sasl_oauthbearer_clientconfig">OAUTHBEARER</a>.
+                    For example, <a href="#security_sasl_gssapi_clientconfig">GSSAPI</a>
+                    credentials may be configured as:
+                    <pre class="brush: text;">
+        KafkaClient {
+        com.sun.security.auth.module.Krb5LoginModule required
+        useKeyTab=true
+        storeKey=true
+        keyTab="/etc/security/keytabs/kafka_client.keytab"
+        principal="kafka-client-1@EXAMPLE.COM";
+    };</pre>
+                </li>
+                <li>Pass the JAAS config file location as JVM parameter to each client JVM. For example:
+                    <pre class="brush: bash;">    -Djava.security.auth.login.config=/etc/kafka/kafka_client_jaas.conf</pre></li>
+	</ol>
+                </li>
+            </ol>
+            </li>
+        </ol>
+    </li>
+    <li><h4><a id="security_sasl_config"
+        href="#security_sasl_config">SASL configuration</a></h4>
+
+        <p>SASL may be used with PLAINTEXT or SSL as the transport layer using the
+        security protocol SASL_PLAINTEXT or SASL_SSL respectively. If SASL_SSL is
+        used, then <a href="#security_ssl">SSL must also be configured</a>.</p>
+
+        <ol>
+        <li><h5><a id="security_sasl_mechanism"
+            href="#security_sasl_mechanism">SASL mechanisms</a></h5>
+            Kafka supports the following SASL mechanisms:
+            <ul>
+                <li><a href="#security_sasl_kerberos">GSSAPI</a> (Kerberos)</li>
+                <li><a href="#security_sasl_plain">PLAIN</a></li>
+                <li><a href="#security_sasl_scram">SCRAM-SHA-256</a></li>
+                <li><a href="#security_sasl_scram">SCRAM-SHA-512</a></li>
+                <li><a href="#security_sasl_oauthbearer">OAUTHBEARER</a></li>
+            </ul>
+        </li>
+        <li><h5><a id="security_sasl_brokerconfig"
+            href="#security_sasl_brokerconfig">SASL configuration for Kafka brokers</a></h5>
+            <ol>
+            <li>Configure a SASL port in server.properties, by adding at least one of
+                SASL_PLAINTEXT or SASL_SSL to the <i>listeners</i> parameter, which
+                contains one or more comma-separated values:
+                <pre>    listeners=SASL_PLAINTEXT://host.name:port</pre>
+                If you are only configuring a SASL port (or if you want
+                the Kafka brokers to authenticate each other using SASL) then make sure
+                you set the same SASL protocol for inter-broker communication:
+                <pre>    security.inter.broker.protocol=SASL_PLAINTEXT (or SASL_SSL)</pre></li>
+            <li>Select one or more  <a href="#security_sasl_mechanism">supported mechanisms</a>
+                to enable in the broker and follow the steps to configure SASL for the mechanism.
+                To enable multiple mechanisms in the broker, follow the steps
+                <a href="#security_sasl_multimechanism">here</a>.</li>
+            </ol>
+        </li>
+        <li><h5><a id="security_sasl_clientconfig"
+            href="#security_sasl_clientconfig">SASL configuration for Kafka clients</a></h5>
+            <p>SASL authentication is only supported for the new Java Kafka producer and
+            consumer, the older API is not supported.</p>
+
+            <p>To configure SASL authentication on the clients, select a SASL
+            <a href="#security_sasl_mechanism">mechanism</a> that is enabled in
+            the broker for client authentication and follow the steps to configure SASL
+            for the selected mechanism.</p></li>
+        </ol>
+    </li>
+    <li><h4><a id="security_sasl_kerberos" href="#security_sasl_kerberos">Authentication using SASL/Kerberos</a></h4>
+        <ol>
+        <li><h5><a id="security_sasl_kerberos_prereq" href="#security_sasl_kerberos_prereq">Prerequisites</a></h5>
+        <ol>
+            <li><b>Kerberos</b><br>
+            If your organization is already using a Kerberos server (for example, by using Active Directory), there is no need to install a new server just for Kafka. Otherwise you will need to install one, your Linux vendor likely has packages for Kerberos and a short guide on how to install and configure it (<a href="https://help.ubuntu.com/community/Kerberos">Ubuntu</a>, <a href="https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Managing_Smart_Cards/install [...]
+            <li><b>Create Kerberos Principals</b><br>
+            If you are using the organization's Kerberos or Active Directory server, ask your Kerberos administrator for a principal for each Kafka broker in your cluster and for every operating system user that will access Kafka with Kerberos authentication (via clients and tools).</br>
+            If you have installed your own Kerberos, you will need to create these principals yourself using the following commands:
+                <pre class="brush: bash;">
+        sudo /usr/sbin/kadmin.local -q 'addprinc -randkey kafka/{hostname}@{REALM}'
+        sudo /usr/sbin/kadmin.local -q "ktadd -k /etc/security/keytabs/{keytabname}.keytab kafka/{hostname}@{REALM}"</pre></li>
+            <li><b>Make sure all hosts can be reachable using hostnames</b> - it is a Kerberos requirement that all your hosts can be resolved with their FQDNs.</li>
+        </ol>
+        <li><h5><a id="security_sasl_kerberos_brokerconfig" href="#security_sasl_kerberos_brokerconfig">Configuring Kafka Brokers</a></h5>
+        <ol>
+            <li>Add a suitably modified JAAS file similar to the one below to each Kafka broker's config directory, let's call it kafka_server_jaas.conf for this example (note that each broker should have its own keytab):
+            <pre class="brush: text;">
+        KafkaServer {
+            com.sun.security.auth.module.Krb5LoginModule required
+            useKeyTab=true
+            storeKey=true
+            keyTab="/etc/security/keytabs/kafka_server.keytab"
+            principal="kafka/kafka1.hostname.com@EXAMPLE.COM";
+        };
+
+        // Zookeeper client authentication
+        Client {
+        com.sun.security.auth.module.Krb5LoginModule required
+        useKeyTab=true
+        storeKey=true
+        keyTab="/etc/security/keytabs/kafka_server.keytab"
+        principal="kafka/kafka1.hostname.com@EXAMPLE.COM";
+        };</pre>
+
+            </li>
+            <tt>KafkaServer</tt> section in the JAAS file tells the broker which principal to use and the location of the keytab where this principal is stored. It
+            allows the broker to login using the keytab specified in this section. See <a href="#security_sasl_brokernotes">notes</a> for more details on Zookeeper SASL configuration.
+            <li>Pass the JAAS and optionally the krb5 file locations as JVM parameters to each Kafka broker (see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/jgss/tutorials/KerberosReq.html">here</a> for more details):
+                <pre>    -Djava.security.krb5.conf=/etc/kafka/krb5.conf
+        -Djava.security.auth.login.config=/etc/kafka/kafka_server_jaas.conf</pre>
+            </li>
+            <li>Make sure the keytabs configured in the JAAS file are readable by the operating system user who is starting kafka broker.</li>
+            <li>Configure SASL port and SASL mechanisms in server.properties as described <a href="#security_sasl_brokerconfig">here</a>. For example:
+            <pre>    listeners=SASL_PLAINTEXT://host.name:port
+        security.inter.broker.protocol=SASL_PLAINTEXT
+        sasl.mechanism.inter.broker.protocol=GSSAPI
+        sasl.enabled.mechanisms=GSSAPI
+            </pre>
+            </li>We must also configure the service name in server.properties, which should match the principal name of the kafka brokers. In the above example, principal is "kafka/kafka1.hostname.com@EXAMPLE.com", so:
+            <pre>    sasl.kerberos.service.name=kafka</pre>
+
+        </ol></li>
+        <li><h5><a id="security_sasl_kerberos_clientconfig" href="#security_sasl_kerberos_clientconfig">Configuring Kafka Clients</a></h5>
+            To configure SASL authentication on the clients:
+            <ol>
+                <li>
+                    Clients (producers, consumers, connect workers, etc) will authenticate to the cluster with their
+                    own principal (usually with the same name as the user running the client), so obtain or create
+                    these principals as needed. Then configure the JAAS configuration property for each client.
+                    Different clients within a JVM may run as different users by specifiying different principals.
+                    The property <code>sasl.jaas.config</code> in producer.properties or consumer.properties describes
+                    how clients like producer and consumer can connect to the Kafka Broker. The following is an example
+                    configuration for a client using a keytab (recommended for long-running processes):
+                <pre>
+    sasl.jaas.config=com.sun.security.auth.module.Krb5LoginModule required \
+        useKeyTab=true \
+        storeKey=true  \
+        keyTab="/etc/security/keytabs/kafka_client.keytab" \
+        principal="kafka-client-1@EXAMPLE.COM";</pre>
+
+                   For command-line utilities like kafka-console-consumer or kafka-console-producer, kinit can be used
+                   along with "useTicketCache=true" as in:
+                <pre>
+    sasl.jaas.config=com.sun.security.auth.module.Krb5LoginModule required \
+        useTicketCache=true;</pre>
+
+                   JAAS configuration for clients may alternatively be specified as a JVM parameter similar to brokers
+                   as described <a href="#security_client_staticjaas">here</a>. Clients use the login section named
+                   <tt>KafkaClient</tt>. This option allows only one user for all client connections from a JVM.</li>
+                <li>Make sure the keytabs configured in the JAAS configuration are readable by the operating system user who is starting kafka client.</li>
+                <li>Optionally pass the krb5 file locations as JVM parameters to each client JVM (see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/jgss/tutorials/KerberosReq.html">here</a> for more details):
+                <pre>    -Djava.security.krb5.conf=/etc/kafka/krb5.conf</pre></li>
+                <li>Configure the following properties in producer.properties or consumer.properties:
+                <pre>
+    security.protocol=SASL_PLAINTEXT (or SASL_SSL)
+    sasl.mechanism=GSSAPI
+    sasl.kerberos.service.name=kafka</pre></li>
+            </ol>
+        </li>
+        </ol>
+    </li>
+
+    <li><h4><a id="security_sasl_plain" href="#security_sasl_plain">Authentication using SASL/PLAIN</a></h4>
+        <p>SASL/PLAIN is a simple username/password authentication mechanism that is typically used with TLS for encryption to implement secure authentication.
+        Kafka supports a default implementation for SASL/PLAIN which can be extended for production use as described <a href="#security_sasl_plain_production">here</a>.</p>
+        The username is used as the authenticated <code>Principal</code> for configuration of ACLs etc.
+        <ol>
+        <li><h5><a id="security_sasl_plain_brokerconfig" href="#security_sasl_plain_brokerconfig">Configuring Kafka Brokers</a></h5>
+            <ol>
+            <li>Add a suitably modified JAAS file similar to the one below to each Kafka broker's config directory, let's call it kafka_server_jaas.conf for this example:
+                <pre class="brush: text;">
+        KafkaServer {
+            org.apache.kafka.common.security.plain.PlainLoginModule required
+            username="admin"
+            password="admin-secret"
+            user_admin="admin-secret"
+            user_alice="alice-secret";
+        };</pre>
+                This configuration defines two users (<i>admin</i> and <i>alice</i>). The properties <tt>username</tt> and <tt>password</tt>
+                in the <tt>KafkaServer</tt> section are used by the broker to initiate connections to other brokers. In this example,
+                <i>admin</i> is the user for inter-broker communication. The set of properties <tt>user_<i>userName</i></tt> defines
+                the passwords for all users that connect to the broker and the broker validates all client connections including
+                those from other brokers using these properties.</li>
+            <li>Pass the JAAS config file location as JVM parameter to each Kafka broker:
+                <pre>    -Djava.security.auth.login.config=/etc/kafka/kafka_server_jaas.conf</pre></li>
+            <li>Configure SASL port and SASL mechanisms in server.properties as described <a href="#security_sasl_brokerconfig">here</a>. For example:
+                <pre>    listeners=SASL_SSL://host.name:port
+        security.inter.broker.protocol=SASL_SSL
+        sasl.mechanism.inter.broker.protocol=PLAIN
+        sasl.enabled.mechanisms=PLAIN</pre></li>
+            </ol>
+        </li>
+
+        <li><h5><a id="security_sasl_plain_clientconfig" href="#security_sasl_plain_clientconfig">Configuring Kafka Clients</a></h5>
+            To configure SASL authentication on the clients:
+            <ol>
+            <li>Configure the JAAS configuration property for each client in producer.properties or consumer.properties.
+                The login module describes how the clients like producer and consumer can connect to the Kafka Broker.
+                The following is an example configuration for a client for the PLAIN mechanism:
+                <pre class="brush: text;">
+    sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required \
+        username="alice" \
+        password="alice-secret";</pre>
+                <p>The options <tt>username</tt> and <tt>password</tt> are used by clients to configure
+                the user for client connections. In this example, clients connect to the broker as user <i>alice</i>.
+                Different clients within a JVM may connect as different users by specifying different user names
+                and passwords in <code>sasl.jaas.config</code>.</p>
+
+                <p>JAAS configuration for clients may alternatively be specified as a JVM parameter similar to brokers
+                as described <a href="#security_client_staticjaas">here</a>. Clients use the login section named
+                <tt>KafkaClient</tt>. This option allows only one user for all client connections from a JVM.</p></li>
+            <li>Configure the following properties in producer.properties or consumer.properties:
+                <pre>
+    security.protocol=SASL_SSL
+    sasl.mechanism=PLAIN</pre></li>
+            </ol>
+        </li>
+        <li><h5><a id="security_sasl_plain_production" href="#security_sasl_plain_production">Use of SASL/PLAIN in production</a></h5>
+            <ul>
+            <li>SASL/PLAIN should be used only with SSL as transport layer to ensure that clear passwords are not transmitted on the wire without encryption.</li>
+            <li>The default implementation of SASL/PLAIN in Kafka specifies usernames and passwords in the JAAS configuration file as shown
+                <a href="#security_sasl_plain_brokerconfig">here</a>. From Kafka version 2.0 onwards, you can avoid storing clear passwords on disk
+                by configuring your own callback handlers that obtain username and password from an external source using the configuration options
+                <code>sasl.server.callback.handler.class</code> and <code>sasl.client.callback.handler.class</code>.</li>
+            <li>In production systems, external authentication servers may implement password authentication. From Kafka version 2.0 onwards,
+                you can plug in your own callback handlers that use external authentication servers for password verification by configuring
+                <code>sasl.server.callback.handler.class</code>.</li>
+            </ul>
+        </li>
+        </ol>
+    </li>
+
+    <li><h4><a id="security_sasl_scram" href="#security_sasl_scram">Authentication using SASL/SCRAM</a></h4>
+        <p>Salted Challenge Response Authentication Mechanism (SCRAM) is a family of SASL mechanisms that
+        addresses the security concerns with traditional mechanisms that perform username/password authentication
+        like PLAIN and DIGEST-MD5. The mechanism is defined in <a href="https://tools.ietf.org/html/rfc5802">RFC 5802</a>.
+        Kafka supports <a href="https://tools.ietf.org/html/rfc7677">SCRAM-SHA-256</a> and SCRAM-SHA-512 which
+        can be used with TLS to perform secure authentication. The username is used as the authenticated
+        <code>Principal</code> for configuration of ACLs etc. The default SCRAM implementation in Kafka
+        stores SCRAM credentials in Zookeeper and is suitable for use in Kafka installations where Zookeeper
+        is on a private network. Refer to <a href="#security_sasl_scram_security">Security Considerations</a>
+        for more details.</p>
+        <ol>
+        <li><h5><a id="security_sasl_scram_credentials" href="#security_sasl_scram_credentials">Creating SCRAM Credentials</a></h5>
+        <p>The SCRAM implementation in Kafka uses Zookeeper as credential store. Credentials can be created in
+        Zookeeper using <tt>kafka-configs.sh</tt>. For each SCRAM mechanism enabled, credentials must be created
+        by adding a config with the mechanism name. Credentials for inter-broker communication must be created
+        before Kafka brokers are started. Client credentials may be created and updated dynamically and updated
+        credentials will be used to authenticate new connections.</p>
+        <p>Create SCRAM credentials for user <i>alice</i> with password <i>alice-secret</i>:
+        <pre class="brush: bash;">
+    > bin/kafka-configs.sh --zookeeper localhost:2181 --alter --add-config 'SCRAM-SHA-256=[iterations=8192,password=alice-secret],SCRAM-SHA-512=[password=alice-secret]' --entity-type users --entity-name alice
+        </pre>
+        <p>The default iteration count of 4096 is used if iterations are not specified. A random salt is created
+        and the SCRAM identity consisting of salt, iterations, StoredKey and ServerKey are stored in Zookeeper.
+        See <a href="https://tools.ietf.org/html/rfc5802">RFC 5802</a> for details on SCRAM identity and the individual fields.
+        <p>The following examples also require a user <i>admin</i> for inter-broker communication which can be created using:
+        <pre class="brush: bash;">
+    > bin/kafka-configs.sh --zookeeper localhost:2181 --alter --add-config 'SCRAM-SHA-256=[password=admin-secret],SCRAM-SHA-512=[password=admin-secret]' --entity-type users --entity-name admin
+        </pre>
+        <p>Existing credentials may be listed using the <i>--describe</i> option:
+        <pre class="brush: bash;">
+    > bin/kafka-configs.sh --zookeeper localhost:2181 --describe --entity-type users --entity-name alice
+        </pre>
+        <p>Credentials may be deleted for one or more SCRAM mechanisms using the <i>--delete</i> option:
+        <pre class="brush: bash;">
+    > bin/kafka-configs.sh --zookeeper localhost:2181 --alter --delete-config 'SCRAM-SHA-512' --entity-type users --entity-name alice
+        </pre>
+        </li>
+        <li><h5><a id="security_sasl_scram_brokerconfig" href="#security_sasl_scram_brokerconfig">Configuring Kafka Brokers</a></h5>
+            <ol>
+            <li>Add a suitably modified JAAS file similar to the one below to each Kafka broker's config directory, let's call it kafka_server_jaas.conf for this example:
+                <pre>
+    KafkaServer {
+        org.apache.kafka.common.security.scram.ScramLoginModule required
+        username="admin"
+        password="admin-secret";
+    };</pre>
+                The properties <tt>username</tt> and <tt>password</tt> in the <tt>KafkaServer</tt> section are used by
+                the broker to initiate connections to other brokers. In this example, <i>admin</i> is the user for
+                inter-broker communication.</li>
+            <li>Pass the JAAS config file location as JVM parameter to each Kafka broker:
+                <pre>    -Djava.security.auth.login.config=/etc/kafka/kafka_server_jaas.conf</pre></li>
+            <li>Configure SASL port and SASL mechanisms in server.properties as described <a href="#security_sasl_brokerconfig">here</a>.</pre> For example:
+                <pre>
+    listeners=SASL_SSL://host.name:port
+    security.inter.broker.protocol=SASL_SSL
+    sasl.mechanism.inter.broker.protocol=SCRAM-SHA-256 (or SCRAM-SHA-512)
+    sasl.enabled.mechanisms=SCRAM-SHA-256 (or SCRAM-SHA-512)</pre></li>
+            </ol>
+        </li>
+
+        <li><h5><a id="security_sasl_scram_clientconfig" href="#security_sasl_scram_clientconfig">Configuring Kafka Clients</a></h5>
+            To configure SASL authentication on the clients:
+            <ol>
+	    <li>Configure the JAAS configuration property for each client in producer.properties or consumer.properties.
+                The login module describes how the clients like producer and consumer can connect to the Kafka Broker.
+	        The following is an example configuration for a client for the SCRAM mechanisms:
+                <pre class="brush: text;">
+   sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \
+        username="alice" \
+        password="alice-secret";</pre>
+
+                <p>The options <tt>username</tt> and <tt>password</tt> are used by clients to configure
+                the user for client connections. In this example, clients connect to the broker as user <i>alice</i>.
+                Different clients within a JVM may connect as different users by specifying different user names
+                and passwords in <code>sasl.jaas.config</code>.</p>
+
+                <p>JAAS configuration for clients may alternatively be specified as a JVM parameter similar to brokers
+                as described <a href="#security_client_staticjaas">here</a>. Clients use the login section named
+                <tt>KafkaClient</tt>. This option allows only one user for all client connections from a JVM.</p></li>
+            <li>Configure the following properties in producer.properties or consumer.properties:
+                <pre>
+    security.protocol=SASL_SSL
+    sasl.mechanism=SCRAM-SHA-256 (or SCRAM-SHA-512)</pre></li>
+            </ol>
+        </li>
+        <li><h5><a id="security_sasl_scram_security" href="#security_sasl_scram_security">Security Considerations for SASL/SCRAM</a></h5>
+            <ul>
+            <li>The default implementation of SASL/SCRAM in Kafka stores SCRAM credentials in Zookeeper. This
+            is suitable for production use in installations where Zookeeper is secure and on a private network.</li>
+            <li>Kafka supports only the strong hash functions SHA-256 and SHA-512 with a minimum iteration count
+            of 4096. Strong hash functions combined with strong passwords and high iteration counts protect
+            against brute force attacks if Zookeeper security is compromised.</li>
+            <li>SCRAM should be used only with TLS-encryption to prevent interception of SCRAM exchanges. This
+            protects against dictionary or brute force attacks and against impersonation if Zookeeper is compromised.</li>
+            <li>From Kafka version 2.0 onwards, the default SASL/SCRAM credential store may be overridden using custom callback handlers
+            by configuring <code>sasl.server.callback.handler.class</code> in installations where Zookeeper is not secure.</li>
+            <li>For more details on security considerations, refer to
+            <a href="https://tools.ietf.org/html/rfc5802#section-9">RFC 5802</a>.
+            </ul>
+        </li>
+        </ol>
+    </li>
+
+    <li><h4><a id="security_sasl_oauthbearer" href="#security_sasl_oauthbearer">Authentication using SASL/OAUTHBEARER</a></h4>
+        <p>The <a href="https://tools.ietf.org/html/rfc6749">OAuth 2 Authorization Framework</a> "enables a third-party application to obtain limited access to an HTTP service,
+        either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP
+        service, or by allowing the third-party application to obtain access on its own behalf."  The SASL OAUTHBEARER mechanism
+        enables the use of the framework in a SASL (i.e. a non-HTTP) context; it is defined in <a href="https://tools.ietf.org/html/rfc7628">RFC 7628</a>.
+        The default OAUTHBEARER implementation in Kafka creates and validates <a href="https://tools.ietf.org/html/rfc7515#appendix-A.5">Unsecured JSON Web Tokens</a>
+        and is only suitable for use in non-production Kafka installations. Refer to <a href="#security_sasl_oauthbearer_security">Security Considerations</a>
+        for more details.</p>
+        <ol>
+        <li><h5><a id="security_sasl_oauthbearer_brokerconfig" href="#security_sasl_oauthbearer_brokerconfig">Configuring Kafka Brokers</a></h5>
+            <ol>
+            <li>Add a suitably modified JAAS file similar to the one below to each Kafka broker's config directory, let's call it kafka_server_jaas.conf for this example:
+                <pre>
+    KafkaServer {
+        org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required
+        unsecuredLoginStringClaim_sub="admin";
+    };</pre>
+                The property <tt>unsecuredLoginStringClaim_sub</tt> in the <tt>KafkaServer</tt> section is used by
+                the broker when it initiates connections to other brokers. In this example, <i>admin</i> will appear in the
+                subject (<tt>sub</tt>) claim and will be the user for inter-broker communication.</li>
+            <li>Pass the JAAS config file location as JVM parameter to each Kafka broker:
+                <pre>    -Djava.security.auth.login.config=/etc/kafka/kafka_server_jaas.conf</pre></li>
+            <li>Configure SASL port and SASL mechanisms in server.properties as described <a href="#security_sasl_brokerconfig">here</a>.</pre> For example:
+                <pre>
+    listeners=SASL_SSL://host.name:port (or SASL_PLAINTEXT if non-production)
+    security.inter.broker.protocol=SASL_SSL (or SASL_PLAINTEXT if non-production)
+    sasl.mechanism.inter.broker.protocol=OAUTHBEARER
+    sasl.enabled.mechanisms=OAUTHBEARER</pre></li>
+            </ol>
+        </li>
+
+        <li><h5><a id="security_sasl_oauthbearer_clientconfig" href="#security_sasl_oauthbearer_clientconfig">Configuring Kafka Clients</a></h5>
+            To configure SASL authentication on the clients:
+            <ol>
+	    <li>Configure the JAAS configuration property for each client in producer.properties or consumer.properties.
+                The login module describes how the clients like producer and consumer can connect to the Kafka Broker.
+	        The following is an example configuration for a client for the OAUTHBEARER mechanisms:
+                <pre class="brush: text;">
+   sasl.jaas.config=org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required \
+        unsecuredLoginStringClaim_sub="alice";</pre>
+
+                <p>The option <tt>unsecuredLoginStringClaim_sub</tt> is used by clients to configure
+                the subject (<tt>sub</tt>) claim, which determines the user for client connections.
+                In this example, clients connect to the broker as user <i>alice</i>.
+                Different clients within a JVM may connect as different users by specifying different subject (<tt>sub</tt>)
+                claims in <code>sasl.jaas.config</code>.</p>
+
+                <p>JAAS configuration for clients may alternatively be specified as a JVM parameter similar to brokers
+                as described <a href="#security_client_staticjaas">here</a>. Clients use the login section named
+                <tt>KafkaClient</tt>. This option allows only one user for all client connections from a JVM.</p></li>
+            <li>Configure the following properties in producer.properties or consumer.properties:
+                <pre>
+    security.protocol=SASL_SSL (or SASL_PLAINTEXT if non-production)
+    sasl.mechanism=OAUTHBEARER</pre></li>
+             <li>The default implementation of SASL/OAUTHBEARER depends on the jackson-databind library.
+                 Since it's an optional dependency, users have to configure it as a dependency via their build tool.</li>
+            </ol>
+        </li>
+        <li><h5><a id="security_sasl_oauthbearer_unsecured_retrieval" href="#security_sasl_oauthbearer_unsecured_retrieval">Unsecured Token Creation Options for SASL/OAUTHBEARER</a></h5>
+            <ul>
+            <li>The default implementation of SASL/OAUTHBEARER in Kafka creates and validates <a href="https://tools.ietf.org/html/rfc7515#appendix-A.5">Unsecured JSON Web Tokens</a>.
+            While suitable only for non-production use, it does provide the flexibility to create arbitrary tokens in a DEV or TEST environment.</li>
+            <li>Here are the various supported JAAS module options on the client side (and on the broker side if OAUTHBEARER is the inter-broker protocol):
+                 <table>
+                 <tr>
+                 <th>JAAS Module Option for Unsecured Token Creation</th>
+                 <th>Documentation</th>
+                 </tr>
+                 <tr>
+                 <td><tt>unsecuredLoginStringClaim_&lt;claimname&gt;="value"</tt></td>
+                 <td>Creates a <tt>String</tt> claim with the given name and value. Any valid
+                 claim name can be specified except '<tt>iat</tt>' and '<tt>exp</tt>' (these are
+                 automatically generated).</td>
+                 </tr>
+                 <tr>
+                 <td><tt>unsecuredLoginNumberClaim_&lt;claimname&gt;="value"</tt></td>
+                 <td>Creates a <tt>Number</tt> claim with the given name and value. Any valid
+                 claim name can be specified except '<tt>iat</tt>' and '<tt>exp</tt>' (these are
+                 automatically generated).</td>
+                 </tr>
+                 <tr>
+                 <td><tt>unsecuredLoginListClaim_&lt;claimname&gt;="value"</tt></td>
+                 <td>Creates a <tt>String List</tt> claim with the given name and values parsed
+                 from the given value where the first character is taken as the delimiter. For
+                 example: <tt>unsecuredLoginListClaim_fubar="|value1|value2"</tt>. Any valid
+                 claim name can be specified except '<tt>iat</tt>' and '<tt>exp</tt>' (these are
+                 automatically generated).</td>
+                 </tr>
+                 <tr>
+                 <td><tt>unsecuredLoginExtension_&lt;extensionname&gt;="value"</tt></td>
+                 <td>Creates a <tt>String</tt> extension with the given name and value.
+                 For example: <tt>unsecuredLoginExtension_traceId="123"</tt>. A valid extension name
+                 is any sequence of lowercase or uppercase alphabet characters. In addition, the "auth" extension name is reserved.
+                 A valid extension value is any combination of characters with ASCII codes 1-127.
+                 </tr>
+                 <tr>
+                 <td><tt>unsecuredLoginPrincipalClaimName</tt></td>
+                 <td>Set to a custom claim name if you wish the name of the <tt>String</tt>
+                 claim holding the principal name to be something other than '<tt>sub</tt>'.</td>
+                 </tr>
+                 <tr>
+                 <td><tt>unsecuredLoginLifetimeSeconds</tt></td>
+                 <td>Set to an integer value if the token expiration is to be set to something
+                 other than the default value of 3600 seconds (which is 1 hour). The
+                 '<tt>exp</tt>' claim will be set to reflect the expiration time.</td>
+                 </tr>
+                 <tr>
+                 <td><tt>unsecuredLoginScopeClaimName</tt></td>
+                 <td>Set to a custom claim name if you wish the name of the <tt>String</tt> or
+                 <tt>String List</tt> claim holding any token scope to be something other than
+                 '<tt>scope</tt>'.</td>
+                 </tr>
+                 </table>
+            </li>
+            </ul>
+        </li>
+        <li><h5><a id="security_sasl_oauthbearer_unsecured_validation" href="#security_sasl_oauthbearer_unsecured_validation">Unsecured Token Validation Options for SASL/OAUTHBEARER</a></h5>
+            <ul>
+            <li>Here are the various supported JAAS module options on the broker side for <a href="https://tools.ietf.org/html/rfc7515#appendix-A.5">Unsecured JSON Web Token</a> validation:
+                 <table>
+                 <tr>
+                 <th>JAAS Module Option for Unsecured Token Validation</th>
+                 <th>Documentation</th>
+                 </tr>
+                 <tr>
+                 <td><tt>unsecuredValidatorPrincipalClaimName="value"</tt></td>
+                 <td>Set to a non-empty value if you wish a particular <tt>String</tt> claim
+                 holding a principal name to be checked for existence; the default is to check
+                 for the existence of the '<tt>sub</tt>' claim.</td>
+                 </tr>
+                 <tr>
+                 <td><tt>unsecuredValidatorScopeClaimName="value"</tt></td>
+                 <td>Set to a custom claim name if you wish the name of the <tt>String</tt> or
+                 <tt>String List</tt> claim holding any token scope to be something other than
+                 '<tt>scope</tt>'.</td>
+                 </tr>
+                 <tr>
+                 <td><tt>unsecuredValidatorRequiredScope="value"</tt></td>
+                 <td>Set to a space-delimited list of scope values if you wish the
+                 <tt>String/String List</tt> claim holding the token scope to be checked to
+                 make sure it contains certain values.</td>
+                 </tr>
+                 <tr>
+                 <td><tt>unsecuredValidatorAllowableClockSkewMs="value"</tt></td>
+                 <td>Set to a positive integer value if you wish to allow up to some number of
+                 positive milliseconds of clock skew (the default is 0).</td>
+                 </tr>
+                 </table>
+            </li>
+            <li>The default unsecured SASL/OAUTHBEARER implementation may be overridden (and must be overridden in production environments)
+            using custom login and SASL Server callback handlers.</li>
+            <li>For more details on security considerations, refer to <a href="https://tools.ietf.org/html/rfc6749#section-10">RFC 6749, Section 10</a>.</li>
+            </ul>
+        </li>
+        <li><h5><a id="security_sasl_oauthbearer_refresh" href="#security_sasl_oauthbearer_refresh">Token Refresh for SASL/OAUTHBEARER</a></h5>
+        Kafka periodically refreshes any token before it expires so that the client can continue to make
+        connections to brokers. The parameters that impact how the refresh algorithm
+        operates are specified as part of the producer/consumer/broker configuration
+        and are as follows. See the documentation for these properties elsewhere for
+        details.  The default values are usually reasonable, in which case these
+        configuration parameters would not need to be explicitly set.
+        <table>
+        <tr>
+        <th>Producer/Consumer/Broker Configuration Property</th>
+        </tr>
+        <tr>
+        <td><tt>sasl.login.refresh.window.factor</tt></td>
+        </tr>
+        <tr>
+        <td><tt>sasl.login.refresh.window.jitter</tt></td>
+        </tr>
+        <tr>
+        <td><tt>sasl.login.refresh.min.period.seconds</tt></td>
+        </tr>
+        <tr>
+        <td><tt>sasl.login.refresh.min.buffer.seconds</tt></td>
+        </tr>
+        </table>
+        </li>
+        <li><h5><a id="security_sasl_oauthbearer_prod" href="#security_sasl_oauthbearer_prod">Secure/Production Use of SASL/OAUTHBEARER</a></h5>
+        Production use cases will require writing an implementation of
+        <tt>org.apache.kafka.common.security.auth.AuthenticateCallbackHandler</tt> that can handle an instance of
+        <tt>org.apache.kafka.common.security.oauthbearer.OAuthBearerTokenCallback</tt> and declaring it via either the
+        <tt>sasl.login.callback.handler.class</tt> configuration option for a
+        non-broker client or via the
+        <tt>listener.name.sasl_ssl.oauthbearer.sasl.login.callback.handler.class</tt>
+        configuration option for brokers (when SASL/OAUTHBEARER is the inter-broker
+        protocol).
+        <p>
+        Production use cases will also require writing an implementation of
+        <tt>org.apache.kafka.common.security.auth.AuthenticateCallbackHandler</tt> that can handle an instance of
+        <tt>org.apache.kafka.common.security.oauthbearer.OAuthBearerValidatorCallback</tt> and declaring it via the
+        <tt>listener.name.sasl_ssl.oauthbearer.sasl.server.callback.handler.class</tt>
+        broker configuration option.
+        </li>
+        <li><h5><a id="security_sasl_oauthbearer_security" href="#security_sasl_oauthbearer_security">Security Considerations for SASL/OAUTHBEARER</a></h5>
+            <ul>
+            <li>The default implementation of SASL/OAUTHBEARER in Kafka creates and validates <a href="https://tools.ietf.org/html/rfc7515#appendix-A.5">Unsecured JSON Web Tokens</a>.
+            This is suitable only for non-production use.</li>
+            <li>OAUTHBEARER should be used in production enviromnments only with TLS-encryption to prevent interception of tokens.</li>
+            <li>The default unsecured SASL/OAUTHBEARER implementation may be overridden (and must be overridden in production environments)
+            using custom login and SASL Server callback handlers as described above.</li>
+            <li>For more details on OAuth 2 security considerations in general, refer to <a href="https://tools.ietf.org/html/rfc6749#section-10">RFC 6749, Section 10</a>.</li>
+            </ul>
+        </li>
+        </ol>
+    </li>
+
+    <li><h4><a id="security_sasl_multimechanism" href="#security_sasl_multimechanism">Enabling multiple SASL mechanisms in a broker</a></h4>
+        <ol>
+        <li>Specify configuration for the login modules of all enabled mechanisms in the <tt>KafkaServer</tt> section of the JAAS config file. For example:
+            <pre>
+        KafkaServer {
+            com.sun.security.auth.module.Krb5LoginModule required
+            useKeyTab=true
+            storeKey=true
+            keyTab="/etc/security/keytabs/kafka_server.keytab"
+            principal="kafka/kafka1.hostname.com@EXAMPLE.COM";
+
+            org.apache.kafka.common.security.plain.PlainLoginModule required
+            username="admin"
+            password="admin-secret"
+            user_admin="admin-secret"
+            user_alice="alice-secret";
+        };</pre></li>
+        <li>Enable the SASL mechanisms in server.properties: <pre>    sasl.enabled.mechanisms=GSSAPI,PLAIN,SCRAM-SHA-256,SCRAM-SHA-512,OAUTHBEARER</pre></li>
+        <li>Specify the SASL security protocol and mechanism for inter-broker communication in server.properties if required:
+            <pre>
+    security.inter.broker.protocol=SASL_PLAINTEXT (or SASL_SSL)
+    sasl.mechanism.inter.broker.protocol=GSSAPI (or one of the other enabled mechanisms)</pre></li>
+        <li>Follow the mechanism-specific steps in <a href="#security_sasl_kerberos_brokerconfig">GSSAPI (Kerberos)</a>,
+            <a href="#security_sasl_plain_brokerconfig">PLAIN</a>,
+            <a href="#security_sasl_scram_brokerconfig">SCRAM</a> and <a href="#security_sasl_oauthbearer_brokerconfig">OAUTHBEARER</a>
+            to configure SASL for the enabled mechanisms.</li>
+        </ol>
+    </li>
+    <li><h4><a id="saslmechanism_rolling_upgrade" href="#saslmechanism_rolling_upgrade">Modifying SASL mechanism in a Running Cluster</a></h4>
+        <p>SASL mechanism can be modified in a running cluster using the following sequence:</p>
+        <ol>
+        <li>Enable new SASL mechanism by adding the mechanism to <tt>sasl.enabled.mechanisms</tt> in server.properties for each broker. Update JAAS config file to include both
+            mechanisms as described <a href="#security_sasl_multimechanism">here</a>. Incrementally bounce the cluster nodes.</li>
+        <li>Restart clients using the new mechanism.</li>
+        <li>To change the mechanism of inter-broker communication (if this is required), set <tt>sasl.mechanism.inter.broker.protocol</tt> in server.properties to the new mechanism and
+            incrementally bounce the cluster again.</li>
+        <li>To remove old mechanism (if this is required), remove the old mechanism from <tt>sasl.enabled.mechanisms</tt> in server.properties and remove the entries for the
+            old mechanism from JAAS config file. Incrementally bounce the cluster again.</li>
+        </ol>
+    </li>
+
+    <li><h4><a id="security_delegation_token" href="#security_delegation_token">Authentication using Delegation Tokens</a></h4>
+        <p>Delegation token based authentication is a lightweight authentication mechanism to complement existing SASL/SSL
+            methods. Delegation tokens are shared secrets between kafka brokers and clients. Delegation tokens will help processing
+            frameworks to distribute the workload to available workers in a secure environment without the added cost of distributing
+            Kerberos TGT/keytabs or keystores when 2-way SSL is used. See <a href="https://cwiki.apache.org/confluence/display/KAFKA/KIP-48+Delegation+token+support+for+Kafka">KIP-48</a>
+            for more details.</p>
+
+        <p>Typical steps for delegation token usage are:</p>
+        <ol>
+        <li>User authenticates with the Kafka cluster via SASL or SSL, and obtains a delegation token. This can be done
+            using AdminClient APIs or using <tt>kafka-delegation-token.sh</tt> script.</li>
+        <li>User securely passes the delegation token to Kafka clients for authenticating with the Kafka cluster.</li>
+        <li>Token owner/renewer can renew/expire the delegation tokens.</li>
+        </ol>
+
+        <ol>
+        <li><h5><a id="security_token_management" href="#security_token_management">Token Management</a></h5>
+        <p> A master key/secret is used to generate and verify delegation tokens. This is supplied using config
+            option <tt>delegation.token.master.key</tt>. Same secret key must be configured across all the brokers.
+            If the secret is not set or set to empty string, brokers will disable the delegation token authentication.</p>
+
+        <p>In current implementation, token details are stored in Zookeeper and is suitable for use in Kafka installations where
+            Zookeeper is on a private network. Also currently,  master key/secret is stored as plain text in server.properties
+            config file. We intend to make these configurable in a future Kafka release.</p>
+
+        <p>A token has a current life, and a maximum renewable life. By default, tokens must be renewed once every 24 hours
+            for up to 7 days. These can be configured using <tt>delegation.token.expiry.time.ms</tt>
+            and <tt>delegation.token.max.lifetime.ms</tt> config options.</p>
+
+        <p>Tokens can also be cancelled explicitly.  If a token is not renewed by the token’s expiration time or if token
+            is beyond the max life time, it will be deleted from all broker caches as well as from zookeeper.</p>
+        </li>
+
+        <li><h5><a id="security_sasl_create_tokens" href="#security_sasl_create_tokens">Creating Delegation Tokens</a></h5>
+        <p>Tokens can be created by using AdminClient APIs or using <tt>kafka-delegation-token.sh</tt> script.
+            Delegation token requests (create/renew/expire/describe) should be issued only on SASL or SSL authenticated channels.
+            Tokens can not be requests if the initial authentication is done through delegation token.
+            <tt>kafka-delegation-token.sh</tt> script examples are given below.</p>
+        <p>Create a delegation token:
+        <pre class="brush: bash;">
+    > bin/kafka-delegation-tokens.sh --bootstrap-server localhost:9092 --create   --max-life-time-period -1 --command-config client.properties --renewer-principal User:user1
+        </pre>
+        <p>Renew a delegation token:
+        <pre class="brush: bash;">
+    > bin/kafka-delegation-tokens.sh --bootstrap-server localhost:9092 --renew    --renew-time-period -1 --command-config client.properties --hmac ABCDEFGHIJK
+        </pre>
+        <p>Expire a delegation token:
+        <pre class="brush: bash;">
+    > bin/kafka-delegation-tokens.sh --bootstrap-server localhost:9092 --expire   --expiry-time-period -1   --command-config client.properties  --hmac ABCDEFGHIJK
+        </pre>
+        <p>Existing tokens can be described using the --describe option:
+        <pre class="brush: bash;">
+    > bin/kafka-delegation-tokens.sh --bootstrap-server localhost:9092 --describe --command-config client.properties  --owner-principal User:user1
+        </pre>
+        </li>
+        <li><h5><a id="security_token_authentication" href="#security_token_authentication">Token Authentication</a></h5>
+            <p>Delegation token authentication piggybacks on the current SASL/SCRAM authentication mechanism. We must enable
+                SASL/SCRAM mechanism on Kafka cluster as described in <a href="#security_sasl_scram">here</a>.</p>
+
+             <p>Configuring Kafka Clients:</p>
+                <ol>
+                    <li>Configure the JAAS configuration property for each client in producer.properties or consumer.properties.
+                The login module describes how the clients like producer and consumer can connect to the Kafka Broker.
+	        The following is an example configuration for a client for the token authentication:
+                <pre class="brush: text;">
+   sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \
+        username="tokenID123" \
+        password="lAYYSFmLs4bTjf+lTZ1LCHR/ZZFNA==" \
+        tokenauth="true";</pre>
+
+                <p>The options <tt>username</tt> and <tt>password</tt> are used by clients to configure the token id and
+                    token HMAC. And the option <tt>tokenauth</tt> is used to indicate the server about token authentication.
+                    In this example, clients connect to the broker using token id: <i>tokenID123</i>. Different clients within a
+                    JVM may connect using different tokens by specifying different token details in <code>sasl.jaas.config</code>.</p>
+
+                <p>JAAS configuration for clients may alternatively be specified as a JVM parameter similar to brokers
+                as described <a href="#security_client_staticjaas">here</a>. Clients use the login section named
+                <tt>KafkaClient</tt>. This option allows only one user for all client connections from a JVM.</p></li>
+            </ol>
+        </li>
+
+        <li><h5><a id="security_token_secret_rotation" href="#security_token_secret_rotation">Procedure to manually rotate the secret:</a></h5>
+            <p>We require a re-deployment when the secret needs to be rotated. During this process, already connected clients
+                will continue to work. But any new connection requests and renew/expire requests with old tokens can fail. Steps are given below.</p>
+
+            <ol>
+                <li>Expire all existing tokens.</li>
+                <li>Rotate the secret by rolling upgrade, and</li>
+                <li>Generate new tokens</li>
+            </ol>
+            <p>We intend to automate this in a future Kafka release.</p>
+        </li>
+
+        <li><h5><a id="security_token_notes" href="#security_token_notes">Notes on Delegation Tokens</a></h5>
+            <ul>
+            <li>Currently, we only allow a user to create delegation token for that user only. Owner/Renewers can renew or expire tokens.
+                Owner/renewers can always describe their own tokens. To describe others tokens, we need to add DESCRIBE permission on Token Resource.</li>
+            </ul>
+        </li>
+        </ol>
+    </li>
+    </ol>
+
+    <h3><a id="security_authz" href="#security_authz">7.4 Authorization and ACLs</a></h3>
+    Kafka ships with a pluggable Authorizer and an out-of-box authorizer implementation that uses zookeeper to store all the acls. The Authorizer is configured by setting <code>authorizer.class.name</code> in server.properties. To enable the out of the box implementation use:
+    <pre>authorizer.class.name=kafka.security.auth.SimpleAclAuthorizer</pre>
+    Kafka acls are defined in the general format of "Principal P is [Allowed/Denied] Operation O From Host H on any Resource R matching ResourcePattern RP". You can read more about the acl structure in KIP-11 and resource patterns in KIP-290. In order to add, remove or list acls you can use the Kafka authorizer CLI. By default, if no ResourcePatterns match a specific Resource R, then R has no associated acls, and therefore no one other than super users is allowed to access R. If you want [...]
+    <pre>allow.everyone.if.no.acl.found=true</pre>
+    One can also add super users in server.properties like the following (note that the delimiter is semicolon since SSL user names may contain comma). Default PrincipalType string "User" is case sensitive.
+    <pre>super.users=User:Bob;User:Alice</pre>
+    By default, the SSL user name will be of the form "CN=writeuser,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown". One can change that by setting a customized PrincipalBuilder in server.properties like the following.
+    <pre>principal.builder.class=CustomizedPrincipalBuilderClass</pre>
+    By default, the SASL user name will be the primary part of the Kerberos principal. One can change that by setting <code>sasl.kerberos.principal.to.local.rules</code> to a customized rule in server.properties.
+    The format of <code>sasl.kerberos.principal.to.local.rules</code> is a list where each rule works in the same way as the auth_to_local in <a href="http://web.mit.edu/Kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html">Kerberos configuration file (krb5.conf)</a>. This also support additional lowercase rule, to force the translated result to be all lower case. This is done by adding a "/L" to the end of the rule. check below formats for syntax.
+    Each rules starts with RULE: and contains an expression as the following formats. See the kerberos documentation for more details.
+    <pre>
+        RULE:[n:string](regexp)s/pattern/replacement/
+        RULE:[n:string](regexp)s/pattern/replacement/g
+        RULE:[n:string](regexp)s/pattern/replacement//L
+        RULE:[n:string](regexp)s/pattern/replacement/g/L
+    </pre>
+
+    An example of adding a rule to properly translate user@MYDOMAIN.COM to user while also keeping the default rule in place is:
+    <pre>sasl.kerberos.principal.to.local.rules=RULE:[1:$1@$0](.*@MYDOMAIN.COM)s/@.*//,DEFAULT</pre>
+
+    <h4><a id="security_authz_cli" href="#security_authz_cli">Command Line Interface</a></h4>
+    Kafka Authorization management CLI can be found under bin directory with all the other CLIs. The CLI script is called <b>kafka-acls.sh</b>. Following lists all the options that the script supports:
+    <p></p>
+    <table class="data-table">
+        <tr>
+            <th>Option</th>
+            <th>Description</th>
+            <th>Default</th>
+            <th>Option type</th>
+        </tr>
+        <tr>
+            <td>--add</td>
+            <td>Indicates to the script that user is trying to add an acl.</td>
+            <td></td>
+            <td>Action</td>
+        </tr>
+        <tr>
+            <td>--remove</td>
+            <td>Indicates to the script that user is trying to remove an acl.</td>
+            <td></td>
+            <td>Action</td>
+        </tr>
+        <tr>
+            <td>--list</td>
+            <td>Indicates to the script that user is trying to list acls.</td>
+            <td></td>
+            <td>Action</td>
+        </tr>
+        <tr>
+            <td>--authorizer</td>
+            <td>Fully qualified class name of the authorizer.</td>
+            <td>kafka.security.auth.SimpleAclAuthorizer</td>
+            <td>Configuration</td>
+        </tr>
+        <tr>
+            <td>--authorizer-properties</td>
+            <td>key=val pairs that will be passed to authorizer for initialization. For the default authorizer the example values are: zookeeper.connect=localhost:2181</td>
+            <td></td>
+            <td>Configuration</td>
+        </tr>
+        <tr>
+            <td>--bootstrap-server</td>
+            <td>A list of host/port pairs to use for establishing the connection to the Kafka cluster. Only one of --bootstrap-server or --authorizer option must be specified.</td>
+            <td></td>
+            <td>Configuration</td>
+        </tr>
+        <tr>
+            <td>--command-config</td>
+            <td>A property file containing configs to be passed to Admin Client. This option can only be used with --bootstrap-server option.</td>
+            <td></td>
+            <td>Configuration</td>
+        </tr>
+        <tr>
+            <td>--cluster</td>
+            <td>Indicates to the script that the user is trying to interact with acls on the singular cluster resource.</td>
+            <td></td>
+            <td>ResourcePattern</td>
+        </tr>
+        <tr>
+            <td>--topic [topic-name]</td>
+            <td>Indicates to the script that the user is trying to interact with acls on topic resource pattern(s).</td>
+            <td></td>
+            <td>ResourcePattern</td>
+        </tr>
+        <tr>
+            <td>--group [group-name]</td>
+            <td>Indicates to the script that the user is trying to interact with acls on consumer-group resource pattern(s)</td>
+            <td></td>
+            <td>ResourcePattern</td>
+        </tr>
+        <tr>
+            <td>--resource-pattern-type [pattern-type]</td>
+            <td>Indicates to the script the type of resource pattern, (for --add), or resource pattern filter, (for --list and --remove), the user wishes to use.<br>
+                When adding acls, this should be a specific pattern type, e.g. 'literal' or 'prefixed'.<br>
+                When listing or removing acls, a specific pattern type filter can be used to list or remove acls from a specific type of resource pattern,
+                or the filter values of 'any' or 'match' can be used, where 'any' will match any pattern type, but will match the resource name exactly,
+                and 'match' will perform pattern matching to list or remove all acls that affect the supplied resource(s).<br>
+                WARNING: 'match', when used in combination with the '--remove' switch, should be used with care.
+            </td>
+            <td>literal</td>
+            <td>Configuration</td>
+        </tr>
+        <tr>
+            <td>--allow-principal</td>
+            <td>Principal is in PrincipalType:name format that will be added to ACL with Allow permission. Default PrincipalType string "User" is case sensitive. <br>You can specify multiple --allow-principal in a single command.</td>
+            <td></td>
+            <td>Principal</td>
+        </tr>
+        <tr>
+            <td>--deny-principal</td>
+            <td>Principal is in PrincipalType:name format that will be added to ACL with Deny permission. Default PrincipalType string "User" is case sensitive. <br>You can specify multiple --deny-principal in a single command.</td>
+            <td></td>
+            <td>Principal</td>
+        </tr>
+        <tr>
+            <td>--principal</td>
+            <td>Principal is in PrincipalType:name format that will be used along with --list option. Default PrincipalType string "User" is case sensitive. This will list the ACLs for the specified principal. <br>You can specify multiple --principal in a single command.</td>
+            <td></td>
+            <td>Principal</td>
+        </tr>
+        <tr>
+            <td>--allow-host</td>
+            <td>IP address from which principals listed in --allow-principal will have access.</td>
+            <td> if --allow-principal is specified defaults to * which translates to "all hosts"</td>
+            <td>Host</td>
+        </tr>
+        <tr>
+            <td>--deny-host</td>
+            <td>IP address from which principals listed in --deny-principal will be denied access.</td>
+            <td>if --deny-principal is specified defaults to * which translates to "all hosts"</td>
+            <td>Host</td>
+        </tr>
+        <tr>
+            <td>--operation</td>
+            <td>Operation that will be allowed or denied.<br>
+                Valid values are : Read, Write, Create, Delete, Alter, Describe, ClusterAction, All</td>
+            <td>All</td>
+            <td>Operation</td>
+        </tr>
+        <tr>
+            <td>--producer</td>
+            <td> Convenience option to add/remove acls for producer role. This will generate acls that allows WRITE,
+                DESCRIBE and CREATE on topic.</td>
+            <td></td>
+            <td>Convenience</td>
+        </tr>
+        <tr>
+            <td>--consumer</td>
+            <td> Convenience option to add/remove acls for consumer role. This will generate acls that allows READ,
+                DESCRIBE on topic and READ on consumer-group.</td>
+            <td></td>
+            <td>Convenience</td>
+        </tr>
+        <tr>
+            <td>--force</td>
+            <td> Convenience option to assume yes to all queries and do not prompt.</td>
+            <td></td>
+            <td>Convenience</td>
+        </tr>
+    </tbody></table>
+
+    <h4><a id="security_authz_examples" href="#security_authz_examples">Examples</a></h4>
+    <ul>
+        <li><b>Adding Acls</b><br>
+    Suppose you want to add an acl "Principals User:Bob and User:Alice are allowed to perform Operation Read and Write on Topic Test-Topic from IP 198.51.100.0 and IP 198.51.100.1". You can do that by executing the CLI with following options:
+            <pre class="brush: bash;">bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --add --allow-principal User:Bob --allow-principal User:Alice --allow-host 198.51.100.0 --allow-host 198.51.100.1 --operation Read --operation Write --topic Test-topic</pre>
+            By default, all principals that don't have an explicit acl that allows access for an operation to a resource are denied. In rare cases where an allow acl is defined that allows access to all but some principal we will have to use the --deny-principal and --deny-host option. For example, if we want to allow all users to Read from Test-topic but only deny User:BadBob from IP 198.51.100.3 we can do so using following commands:
+            <pre class="brush: bash;">bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --add --allow-principal User:* --allow-host * --deny-principal User:BadBob --deny-host 198.51.100.3 --operation Read --topic Test-topic</pre>
+            Note that ``--allow-host`` and ``deny-host`` only support IP addresses (hostnames are not supported).
+            Above examples add acls to a topic by specifying --topic [topic-name] as the resource pattern option. Similarly user can add acls to cluster by specifying --cluster and to a consumer group by specifying --group [group-name].
+            You can add acls on any resource of a certain type, e.g. suppose you wanted to add an acl "Principal User:Peter is allowed to produce to any Topic from IP 198.51.200.0"
+            You can do that by using the wildcard resource '*', e.g. by executing the CLI with following options:
+            <pre class="brush: bash;">bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --add --allow-principal User:Peter --allow-host 198.51.200.1 --producer --topic *</pre>
+            You can add acls on prefixed resource patterns, e.g. suppose you want to add an acl "Principal User:Jane is allowed to produce to any Topic whose name starts with 'Test-' from any host".
+            You can do that by executing the CLI with following options:
+            <pre class="brush: bash;">bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --add --allow-principal User:Jane --producer --topic Test- --resource-pattern-type prefixed</pre>
+            Note, --resource-pattern-type defaults to 'literal', which only affects resources with the exact same name or, in the case of the wildcard resource name '*', a resource with any name.</li>
+
+        <li><b>Removing Acls</b><br>
+                Removing acls is pretty much the same. The only difference is instead of --add option users will have to specify --remove option. To remove the acls added by the first example above we can execute the CLI with following options:
+            <pre class="brush: bash;"> bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --remove --allow-principal User:Bob --allow-principal User:Alice --allow-host 198.51.100.0 --allow-host 198.51.100.1 --operation Read --operation Write --topic Test-topic </pre>
+            If you wan to remove the acl added to the prefixed resource pattern above we can execute the CLI with following options:
+            <pre class="brush: bash;"> bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --remove --allow-principal User:Jane --producer --topic Test- --resource-pattern-type Prefixed</pre></li>
+
+        <li><b>List Acls</b><br>
+                We can list acls for any resource by specifying the --list option with the resource. To list all acls on the literal resource pattern Test-topic, we can execute the CLI with following options:
+                <pre class="brush: bash;">bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --list --topic Test-topic</pre>
+                However, this will only return the acls that have been added to this exact resource pattern. Other acls can exist that affect access to the topic,
+                e.g. any acls on the topic wildcard '*', or any acls on prefixed resource patterns. Acls on the wildcard resource pattern can be queried explicitly:
+                <pre class="brush: bash;">bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --list --topic *</pre>
+                However, it is not necessarily possible to explicitly query for acls on prefixed resource patterns that match Test-topic as the name of such patterns may not be known.
+                We can list <i>all</i> acls affecting Test-topic by using '--resource-pattern-type match', e.g.
+                <pre class="brush: bash;">bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --list --topic Test-topic --resource-pattern-type match</pre>
+                This will list acls on all matching literal, wildcard and prefixed resource patterns.</li>
+
+        <li><b>Adding or removing a principal as producer or consumer</b><br>
+                The most common use case for acl management are adding/removing a principal as producer or consumer so we added convenience options to handle these cases. In order to add User:Bob as a producer of  Test-topic we can execute the following command:
+            <pre class="brush: bash;"> bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --add --allow-principal User:Bob --producer --topic Test-topic</pre>
+                Similarly to add Alice as a consumer of Test-topic with consumer group Group-1 we just have to pass --consumer option:
+            <pre class="brush: bash;"> bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --add --allow-principal User:Bob --consumer --topic Test-topic --group Group-1 </pre>
+                Note that for consumer option we must also specify the consumer group.
+                In order to remove a principal from producer or consumer role we just need to pass --remove option. </li>
+
+        <li><b>AdminClient API based acl management</b><br>
+            Users having Alter permission on ClusterResource can use AdminClient API for ACL management. kafka-acls.sh script supports AdminClient API to manage ACLs without interacting with zookeeper/authorizer directly.
+            All the above examples can be executed by using <b>--bootstrap-server</b> option. For example:
+
+            <pre class="brush: bash;">
+            bin/kafka-acls.sh --bootstrap-server localhost:9092 --command-config /tmp/adminclient-configs.conf --add --allow-principal User:Bob --producer --topic Test-topic
+            bin/kafka-acls.sh --bootstrap-server localhost:9092 --command-config /tmp/adminclient-configs.conf --add --allow-principal User:Bob --consumer --topic Test-topic --group Group-1
+            bin/kafka-acls.sh --bootstrap-server localhost:9092 --command-config /tmp/adminclient-configs.conf --list --topic Test-topic</pre></li>
+
+    </ul>
+
+    <h3><a id="security_rolling_upgrade" href="#security_rolling_upgrade">7.5 Incorporating Security Features in a Running Cluster</a></h3>
+        You can secure a running cluster via one or more of the supported protocols discussed previously. This is done in phases:
+        <p></p>
+        <ul>
+            <li>Incrementally bounce the cluster nodes to open additional secured port(s).</li>
+            <li>Restart clients using the secured rather than PLAINTEXT port (assuming you are securing the client-broker connection).</li>
+            <li>Incrementally bounce the cluster again to enable broker-to-broker security (if this is required)</li>
+            <li>A final incremental bounce to close the PLAINTEXT port.</li>
+        </ul>
+        <p></p>
+        The specific steps for configuring SSL and SASL are described in sections <a href="#security_ssl">7.2</a> and <a href="#security_sasl">7.3</a>.
+        Follow these steps to enable security for your desired protocol(s).
+        <p></p>
+        The security implementation lets you configure different protocols for both broker-client and broker-broker communication.
+        These must be enabled in separate bounces. A PLAINTEXT port must be left open throughout so brokers and/or clients can continue to communicate.
+        <p></p>
+
+        When performing an incremental bounce stop the brokers cleanly via a SIGTERM. It's also good practice to wait for restarted replicas to return to the ISR list before moving onto the next node.
+        <p></p>
+        As an example, say we wish to encrypt both broker-client and broker-broker communication with SSL. In the first incremental bounce, a SSL port is opened on each node:
+            <pre>
+            listeners=PLAINTEXT://broker1:9091,SSL://broker1:9092
+            </pre>
+
+        We then restart the clients, changing their config to point at the newly opened, secured port:
+
+            <pre>
+            bootstrap.servers = [broker1:9092,...]
+            security.protocol = SSL
+            ...etc
+            </pre>
+
+        In the second incremental server bounce we instruct Kafka to use SSL as the broker-broker protocol (which will use the same SSL port):
+
+            <pre>
+            listeners=PLAINTEXT://broker1:9091,SSL://broker1:9092
+            security.inter.broker.protocol=SSL
+            </pre>
+
+        In the final bounce we secure the cluster by closing the PLAINTEXT port:
+
+            <pre>
+            listeners=SSL://broker1:9092
+            security.inter.broker.protocol=SSL
+            </pre>
+
+        Alternatively we might choose to open multiple ports so that different protocols can be used for broker-broker and broker-client communication. Say we wished to use SSL encryption throughout (i.e. for broker-broker and broker-client communication) but we'd like to add SASL authentication to the broker-client connection also. We would achieve this by opening two additional ports during the first bounce:
+
+            <pre>
+            listeners=PLAINTEXT://broker1:9091,SSL://broker1:9092,SASL_SSL://broker1:9093
+            </pre>
+
+        We would then restart the clients, changing their config to point at the newly opened, SASL & SSL secured port:
+
+            <pre>
+            bootstrap.servers = [broker1:9093,...]
+            security.protocol = SASL_SSL
+            ...etc
+            </pre>
+
+        The second server bounce would switch the cluster to use encrypted broker-broker communication via the SSL port we previously opened on port 9092:
+
+            <pre>
+            listeners=PLAINTEXT://broker1:9091,SSL://broker1:9092,SASL_SSL://broker1:9093
+            security.inter.broker.protocol=SSL
+            </pre>
+
+        The final bounce secures the cluster by closing the PLAINTEXT port.
+
+            <pre>
+        listeners=SSL://broker1:9092,SASL_SSL://broker1:9093
+        security.inter.broker.protocol=SSL
+            </pre>
+
+        ZooKeeper can be secured independently of the Kafka cluster. The steps for doing this are covered in section <a href="#zk_authz_migration">7.6.2</a>.
+
+
+    <h3><a id="zk_authz" href="#zk_authz">7.6 ZooKeeper Authentication</a></h3>
+    <h4><a id="zk_authz_new" href="#zk_authz_new">7.6.1 New clusters</a></h4>
+    To enable ZooKeeper authentication on brokers, there are two necessary steps:
+    <ol>
+        <li> Create a JAAS login file and set the appropriate system property to point to it as described above</li>
+        <li> Set the configuration property <tt>zookeeper.set.acl</tt> in each broker to true</li>
+    </ol>
+
+    The metadata stored in ZooKeeper for the Kafka cluster is world-readable, but can only be modified by the brokers. The rationale behind this decision is that the data stored in ZooKeeper is not sensitive, but inappropriate manipulation of that data can cause cluster disruption. We also recommend limiting the access to ZooKeeper via network segmentation (only brokers and some admin tools need access to ZooKeeper).
+
+    <h4><a id="zk_authz_migration" href="#zk_authz_migration">7.6.2 Migrating clusters</a></h4>
+    If you are running a version of Kafka that does not support security or simply with security disabled, and you want to make the cluster secure, then you need to execute the following steps to enable ZooKeeper authentication with minimal disruption to your operations:
+    <ol>
+        <li>Perform a rolling restart setting the JAAS login file, which enables brokers to authenticate. At the end of the rolling restart, brokers are able to manipulate znodes with strict ACLs, but they will not create znodes with those ACLs</li>
+        <li>Perform a second rolling restart of brokers, this time setting the configuration parameter <tt>zookeeper.set.acl</tt> to true, which enables the use of secure ACLs when creating znodes</li>
+        <li>Execute the ZkSecurityMigrator tool. To execute the tool, there is this script: <tt>./bin/zookeeper-security-migration.sh</tt> with <tt>zookeeper.acl</tt> set to secure. This tool traverses the corresponding sub-trees changing the ACLs of the znodes</li>
+    </ol>
+    <p>It is also possible to turn off authentication in a secure cluster. To do it, follow these steps:</p>
+    <ol>
+        <li>Perform a rolling restart of brokers setting the JAAS login file, which enables brokers to authenticate, but setting <tt>zookeeper.set.acl</tt> to false. At the end of the rolling restart, brokers stop creating znodes with secure ACLs, but are still able to authenticate and manipulate all znodes</li>
+        <li>Execute the ZkSecurityMigrator tool. To execute the tool, run this script <tt>./bin/zookeeper-security-migration.sh</tt> with <tt>zookeeper.acl</tt> set to unsecure. This tool traverses the corresponding sub-trees changing the ACLs of the znodes</li>
+        <li>Perform a second rolling restart of brokers, this time omitting the system property that sets the JAAS login file</li>
+    </ol>
+    Here is an example of how to run the migration tool:
+    <pre class="brush: bash;">
+    ./bin/zookeeper-security-migration.sh --zookeeper.acl=secure --zookeeper.connect=localhost:2181
+    </pre>
+    <p>Run this to see the full list of parameters:</p>
+    <pre class="brush: bash;">
+    ./bin/zookeeper-security-migration.sh --help
+    </pre>
+    <h4><a id="zk_authz_ensemble" href="#zk_authz_ensemble">7.6.3 Migrating the ZooKeeper ensemble</a></h4>
+    It is also necessary to enable authentication on the ZooKeeper ensemble. To do it, we need to perform a rolling restart of the server and set a few properties. Please refer to the ZooKeeper documentation for more detail:
+    <ol>
+        <li><a href="http://zookeeper.apache.org/doc/r3.4.9/zookeeperProgrammers.html#sc_ZooKeeperAccessControl">Apache ZooKeeper documentation</a></li>
+        <li><a href="https://cwiki.apache.org/confluence/display/ZOOKEEPER/Zookeeper+and+SASL">Apache ZooKeeper wiki</a></li>
+    </ol>
+</script>
+
+<div class="p-security"></div>
diff --git a/21/streams/architecture.html b/21/streams/architecture.html
new file mode 100644
index 0000000..8bc3156
--- /dev/null
+++ b/21/streams/architecture.html
@@ -0,0 +1,173 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<script><!--#include virtual="../js/templateData.js" --></script>
+
+<script id="content-template" type="text/x-handlebars-template">
+    <h1>Architecture</h1>
+    <div class="sub-nav-sticky">
+        <div class="sticky-top">
+            <div style="height:35px">
+                <a href="/{{version}}/documentation/streams/">Introduction</a>
+                <a href="/{{version}}/documentation/streams/quickstart">Run Demo App</a>
+                <a href="/{{version}}/documentation/streams/tutorial">Tutorial: Write App</a>
+                <a href="/{{version}}/documentation/streams/core-concepts">Concepts</a>
+                <a class="active-menu-item" href="/{{version}}/documentation/streams/architecture">Architecture</a>
+                <a href="/{{version}}/documentation/streams/developer-guide/">Developer Guide</a>
+                <a href="/{{version}}/documentation/streams/upgrade-guide">Upgrade</a>
+            </div>
+        </div>
+    </div>
+
+    Kafka Streams simplifies application development by building on the Kafka producer and consumer libraries and leveraging the native capabilities of
+    Kafka to offer data parallelism, distributed coordination, fault tolerance, and operational simplicity. In this section, we describe how Kafka Streams works underneath the covers.
+
+    <p>
+        The picture below shows the anatomy of an application that uses the Kafka Streams library. Let's walk through some details.
+    </p>
+    <img class="centered" src="/{{version}}/images/streams-architecture-overview.jpg" style="width:750px">
+
+    <h3><a id="streams_architecture_tasks" href="#streams_architecture_tasks">Stream Partitions and Tasks</a></h3>
+
+    <p>
+        The messaging layer of Kafka partitions data for storing and transporting it. Kafka Streams partitions data for processing it.
+        In both cases, this partitioning is what enables data locality, elasticity, scalability, high performance, and fault tolerance.
+        Kafka Streams uses the concepts of <b>partitions</b> and <b>tasks</b> as logical units of its parallelism model based on Kafka topic partitions.
+        There are close links between Kafka Streams and Kafka in the context of parallelism:
+    </p>
+
+    <ul>
+        <li>Each <b>stream partition</b> is a totally ordered sequence of data records and maps to a Kafka <b>topic partition</b>.</li>
+        <li>A <b>data record</b> in the stream maps to a Kafka <b>message</b> from that topic.</li>
+        <li>The <b>keys</b> of data records determine the partitioning of data in both Kafka and Kafka Streams, i.e., how data is routed to specific partitions within topics.</li>
+    </ul>
+
+    <p>
+        An application's processor topology is scaled by breaking it into multiple tasks.
+        More specifically, Kafka Streams creates a fixed number of tasks based on the input stream partitions for the application,
+        with each task assigned a list of partitions from the input streams (i.e., Kafka topics). The assignment of partitions to tasks
+        never changes so that each task is a fixed unit of parallelism of the application. Tasks can then instantiate their own processor topology
+        based on the assigned partitions; they also maintain a buffer for each of its assigned partitions and process messages one-at-a-time from
+        these record buffers. As a result stream tasks can be processed independently and in parallel without manual intervention.
+    </p>
+
+    <p>
+        It is important to understand that Kafka Streams is not a resource manager, but a library that "runs" anywhere its stream processing application runs.
+        Multiple instances of the application are executed either on the same machine, or spread across multiple machines and tasks can be distributed automatically
+        by the library to those running application instances. The assignment of partitions to tasks never changes; if an application instance fails, all its assigned
+        tasks will be automatically restarted on other instances and continue to consume from the same stream partitions.
+    </p>
+
+    <p>
+        The following diagram shows two tasks each assigned with one partition of the input streams.
+    </p>
+    <img class="centered" src="/{{version}}/images/streams-architecture-tasks.jpg" style="width:400px">
+    <br>
+
+    <h3><a id="streams_architecture_threads" href="#streams_architecture_threads">Threading Model</a></h3>
+
+    <p>
+        Kafka Streams allows the user to configure the number of <b>threads</b> that the library can use to parallelize processing within an application instance.
+        Each thread can execute one or more tasks with their processor topologies independently. For example, the following diagram shows one stream thread running two stream tasks.
+    </p>
+    <img class="centered" src="/{{version}}/images/streams-architecture-threads.jpg" style="width:400px">
+
+    <p>
+        Starting more stream threads or more instances of the application merely amounts to replicating the topology and having it process a different subset of Kafka partitions, effectively parallelizing processing.
+        It is worth noting that there is no shared state amongst the threads, so no inter-thread coordination is necessary. This makes it very simple to run topologies in parallel across the application instances and threads.
+        The assignment of Kafka topic partitions amongst the various stream threads is transparently handled by Kafka Streams leveraging <a href="https://cwiki.apache.org/confluence/display/KAFKA/Kafka+Client-side+Assignment+Proposal">Kafka's coordination</a> functionality.
+    </p>
+
+    <p>
+        As we described above, scaling your stream processing application with Kafka Streams is easy: you merely need to start additional instances of your application,
+        and Kafka Streams takes care of distributing partitions amongst tasks that run in the application instances. You can start as many threads of the application
+        as there are input Kafka topic partitions so that, across all running instances of an application, every thread (or rather, the tasks it runs) has at least one input partition to process.
+    </p>
+    <br>
+
+    <h3><a id="streams_architecture_state" href="#streams_architecture_state">Local State Stores</a></h3>
+
+    <p>
+        Kafka Streams provides so-called <b>state stores</b>, which can be used by stream processing applications to store and query data,
+        which is an important capability when implementing stateful operations. The <a href="/{{version}}/documentation/streams/developer-guide#streams_dsl">Kafka Streams DSL</a>, for example, automatically creates
+        and manages such state stores when you are calling stateful operators such as <code>join()</code> or <code>aggregate()</code>, or when you are windowing a stream.
+    </p>
+
+    <p>
+        Every stream task in a Kafka Streams application may embed one or more local state stores that can be accessed via APIs to store and query data required for processing.
+        Kafka Streams offers fault-tolerance and automatic recovery for such local state stores.
+    </p>
+
+    <p>
+        The following diagram shows two stream tasks with their dedicated local state stores.
+    </p>
+    <img class="centered" src="/{{version}}/images/streams-architecture-states.jpg" style="width:400px">
+    <br>
+
+    <h3><a id="streams_architecture_recovery" href="#streams_architecture_recovery">Fault Tolerance</a></h3>
+
+    <p>
+        Kafka Streams builds on fault-tolerance capabilities integrated natively within Kafka. Kafka partitions are highly available and replicated; so when stream data is persisted to Kafka it is available
+        even if the application fails and needs to re-process it. Tasks in Kafka Streams leverage the fault-tolerance capability
+        offered by the Kafka consumer client to handle failures.
+        If a task runs on a machine that fails, Kafka Streams automatically restarts the task in one of the remaining running instances of the application.
+    </p>
+
+    <p>
+        In addition, Kafka Streams makes sure that the local state stores are robust to failures, too. For each state store, it maintains a replicated changelog Kafka topic in which it tracks any state updates.
+        These changelog topics are partitioned as well so that each local state store instance, and hence the task accessing the store, has its own dedicated changelog topic partition.
+        <a href="/{{version}}/documentation/#compaction">Log compaction</a> is enabled on the changelog topics so that old data can be purged safely to prevent the topics from growing indefinitely.
+        If tasks run on a machine that fails and are restarted on another machine, Kafka Streams guarantees to restore their associated state stores to the content before the failure by
+        replaying the corresponding changelog topics prior to resuming the processing on the newly started tasks. As a result, failure handling is completely transparent to the end user.
+    </p>
+
+    <p>
+        Note that the cost of task (re)initialization typically depends primarily on the time for restoring the state by replaying the state stores' associated changelog topics.
+        To minimize this restoration time, users can configure their applications to have <b>standby replicas</b> of local states (i.e. fully replicated copies of the state).
+        When a task migration happens, Kafka Streams then attempts to assign a task to an application instance where such a standby replica already exists in order to minimize
+        the task (re)initialization cost. See <code>num.standby.replicas</code> in the <a href="/{{version}}/documentation/#streamsconfigs"><b>Kafka Streams Configs</b></a> section.
+    </p>
+
+    <div class="pagination">
+        <a href="/{{version}}/documentation/streams/core-concepts" class="pagination__btn pagination__btn__prev">Previous</a>
+        <a href="/{{version}}/documentation/streams/developer-guide" class="pagination__btn pagination__btn__next">Next</a>
+    </div>
+</script>
+
+<!--#include virtual="../../includes/_header.htm" -->
+<!--#include virtual="../../includes/_top.htm" -->
+<div class="content documentation documentation--current">
+    <!--#include virtual="../../includes/_nav.htm" -->
+    <div class="right">
+        <!--#include virtual="../../includes/_docs_banner.htm" -->
+        <ul class="breadcrumbs">
+            <li><a href="/documentation">Documentation</a></li>
+            <li><a href="/documentation/streams">Kafka Streams</a></li>
+        </ul>
+        <div class="p-content"></div>
+    </div>
+</div>
+<!--#include virtual="../../includes/_footer.htm" -->
+<script>
+$(function() {
+  // Show selected style on nav item
+  $('.b-nav__streams').addClass('selected');
+
+  // Display docs subnav items
+  $('.b-nav__docs').parent().toggleClass('nav__item__with__subs--expanded');
+});
+</script>
diff --git a/21/streams/core-concepts.html b/21/streams/core-concepts.html
new file mode 100644
index 0000000..bffaa8a
--- /dev/null
+++ b/21/streams/core-concepts.html
@@ -0,0 +1,298 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<script><!--#include virtual="../js/templateData.js" --></script>
+
+<script id="content-template" type="text/x-handlebars-template">
+    <h1>Core Concepts</h1>
+    <div class="sub-nav-sticky">
+        <div class="sticky-top">
+            <div style="height:35px">
+                <a href="/{{version}}/documentation/streams/">Introduction</a>
+                <a href="/{{version}}/documentation/streams/quickstart">Run Demo App</a>
+                <a href="/{{version}}/documentation/streams/tutorial">Tutorial: Write App</a>
+                <a class="active-menu-item" href="/{{version}}/documentation/streams/core-concepts">Concepts</a>
+                <a href="/{{version}}/documentation/streams/architecture">Architecture</a>
+                <a href="/{{version}}/documentation/streams/developer-guide/">Developer Guide</a>
+                <a href="/{{version}}/documentation/streams/upgrade-guide">Upgrade</a>
+            </div>
+        </div>
+    </div>
+    <p>
+        Kafka Streams is a client library for processing and analyzing data stored in Kafka.
+        It builds upon important stream processing concepts such as properly distinguishing between event time and processing time, windowing support, and simple yet efficient management and real-time querying of application state.
+    </p>
+    <p>
+        Kafka Streams has a <b>low barrier to entry</b>: You can quickly write and run a small-scale proof-of-concept on a single machine; and you only need to run additional instances of your application on multiple machines to scale up to high-volume production workloads.
+        Kafka Streams transparently handles the load balancing of multiple instances of the same application by leveraging Kafka's parallelism model.
+    </p>
+    <p>
+        Some highlights of Kafka Streams:
+    </p>
+
+    <ul>
+        <li>Designed as a <b>simple and lightweight client library</b>, which can be easily embedded in any Java application and integrated with any existing packaging, deployment and operational tools that users have for their streaming applications.</li>
+        <li>Has <b>no external dependencies on systems other than Apache Kafka itself</b> as the internal messaging layer; notably, it uses Kafka's partitioning model to horizontally scale processing while maintaining strong ordering guarantees.</li>
+        <li>Supports <b>fault-tolerant local state</b>, which enables very fast and efficient stateful operations like windowed joins and aggregations.</li>
+        <li>Supports <b>exactly-once</b> processing semantics to guarantee that each record will be processed once and only once even when there is a failure on either Streams clients or Kafka brokers in the middle of processing.</li>
+        <li>Employs <b>one-record-at-a-time processing</b> to achieve millisecond processing latency, and supports <b>event-time based windowing operations</b> with late arrival of records.</li>
+        <li>Offers necessary stream processing primitives, along with a <b>high-level Streams DSL</b> and a <b>low-level Processor API</b>.</li>
+
+    </ul>
+
+    <p>
+        We first summarize the key concepts of Kafka Streams.
+    </p>
+
+    <h3><a id="streams_topology" href="#streams_topology">Stream Processing Topology</a></h3>
+
+    <ul>
+        <li>A <b>stream</b> is the most important abstraction provided by Kafka Streams: it represents an unbounded, continuously updating data set. A stream is an ordered, replayable, and fault-tolerant sequence of immutable data records, where a <b>data record</b> is defined as a key-value pair.</li>
+        <li>A <b>stream processing application</b> is any program that makes use of the Kafka Streams library. It defines its computational logic through one or more <b>processor topologies</b>, where a processor topology is a graph of stream processors (nodes) that are connected by streams (edges).</li>
+        <li>A <b><a id="streams_processor_node" href="#streams_processor_node">stream processor</a></b> is a node in the processor topology; it represents a processing step to transform data in streams by receiving one input record at a time from its upstream processors in the topology, applying its operation to it, and may subsequently produce one or more output records to its downstream processors. </li>
+    </ul>
+
+    There are two special processors in the topology:
+
+    <ul>
+        <li><b>Source Processor</b>: A source processor is a special type of stream processor that does not have any upstream processors. It produces an input stream to its topology from one or multiple Kafka topics by consuming records from these topics and forwarding them to its down-stream processors.</li>
+        <li><b>Sink Processor</b>: A sink processor is a special type of stream processor that does not have down-stream processors. It sends any received records from its up-stream processors to a specified Kafka topic.</li>
+    </ul>
+
+    Note that in normal processor nodes other remote systems can also be accessed while processing the current record. Therefore the processed results can either be streamed back into Kafka or written to an external system.
+
+    <img class="centered" src="/{{version}}/images/streams-architecture-topology.jpg" style="width:400px">
+
+    <p>
+        Kafka Streams offers two ways to define the stream processing topology: the <a href="/{{version}}/documentation/streams/developer-guide/dsl-api.html"><b>Kafka Streams DSL</b></a> provides
+        the most common data transformation operations such as <code>map</code>, <code>filter</code>, <code>join</code> and <code>aggregations</code> out of the box; the lower-level <a href="/{{version}}/documentation/streams/developer-guide/processor-api.html"><b>Processor API</b></a> allows
+        developers define and connect custom processors as well as to interact with <a href="#streams_state">state stores</a>.
+    </p>
+
+    <p>
+        A processor topology is merely a logical abstraction for your stream processing code.
+        At runtime, the logical topology is instantiated and replicated inside the application for parallel processing (see <a href="/{{version}}/documentation/streams/architecture#streams_architecture_tasks"><b>Stream Partitions and Tasks</b></a> for details).
+    </p>
+
+    <h3><a id="streams_time" href="#streams_time">Time</a></h3>
+
+    <p>
+        A critical aspect in stream processing is the notion of <b>time</b>, and how it is modeled and integrated.
+        For example, some operations such as <b>windowing</b> are defined based on time boundaries.
+    </p>
+    <p>
+        Common notions of time in streams are:
+    </p>
+
+    <ul>
+        <li><b>Event time</b> - The point in time when an event or data record occurred, i.e. was originally created "at the source". <b>Example:</b> If the event is a geo-location change reported by a GPS sensor in a car, then the associated event-time would be the time when the GPS sensor captured the location change.</li>
+        <li><b>Processing time</b> - The point in time when the event or data record happens to be processed by the stream processing application, i.e. when the record is being consumed. The processing time may be milliseconds, hours, or days etc. later than the original event time. <b>Example:</b> Imagine an analytics application that reads and processes the geo-location data reported from car sensors to present it to a fleet management dashboard. Here, processing-time in the analytics  [...]
+        <li><b>Ingestion time</b> - The point in time when an event or data record is stored in a topic partition by a Kafka broker. The difference to event time is that this ingestion timestamp is generated when the record is appended to the target topic by the Kafka broker, not when the record is created "at the source". The difference to processing time is that processing time is when the stream processing application processes the record. <b>For example,</b> if a record is never proc [...]
+    </ul>
+    <p>
+        The choice between event-time and ingestion-time is actually done through the configuration of Kafka (not Kafka Streams): From Kafka 0.10.x onwards, timestamps are automatically embedded into Kafka messages. Depending on Kafka's configuration these timestamps represent event-time or ingestion-time. The respective Kafka configuration setting can be specified on the broker level or per topic. The default timestamp extractor in Kafka Streams will retrieve these embedded timestamps a [...]
+    </p>
+    <p>
+        Kafka Streams assigns a <b>timestamp</b> to every data record via the <code>TimestampExtractor</code> interface.
+        These per-record timestamps describe the progress of a stream with regards to time and are leveraged by	time-dependent operations such as window operations.
+        As a result, this time will only advance when a new record arrives at the processor.
+        We call this data-driven time the <b>stream time</b> of the application to differentiate with the <b>wall-clock time</b> when this application is actually executing.
+        Concrete implementations of the <code>TimestampExtractor</code> interface will then provide different semantics to the stream time definition.
+        For example retrieving or computing timestamps based on the actual contents of data records such as an embedded timestamp field to provide event time semantics,
+        and returning the current wall-clock time thereby yield processing time semantics to stream time.
+        Developers can thus enforce different notions of time depending on their business needs.
+    </p>
+
+    <p>
+        Finally, whenever a Kafka Streams application writes records to Kafka, then it will also assign timestamps to these new records. The way the timestamps are assigned depends on the context:
+    </p>
+
+    <ul>
+        <li> When new output records are generated via processing some input record, for example, <code>context.forward()</code> triggered in the <code>process()</code> function call, output record timestamps are inherited from input record timestamps directly.</li>
+        <li> When new output records are generated via periodic functions such as <code>Punctuator#punctuate()</code>, the output record timestamp is defined as the current internal time (obtained through <code>context.timestamp()</code>) of the stream task.</li>
+        <li> For aggregations, the timestamp of a resulting aggregate update record will be that of the latest arrived input record that triggered the update.</li>
+    </ul>
+
+    <p>
+	Note, that the describe default behavior can be changed in the Processor API by assigning timestamps to output records explicitly when calling <code>#forward()</code>.
+    </p>
+
+    <h3><a id="streams_concepts_aggregations" href="#streams_concepts_aggregations">Aggregations</a></h3>
+    <p>
+        An <strong>aggregation</strong> operation takes one input stream or table, and yields a new table by combining multiple input records into a single output record. Examples of aggregations are computing counts or sum.
+    </p>
+
+    <p>
+        In the <code>Kafka Streams DSL</code>, an input stream of an <code>aggregation</code> can be a KStream or a KTable, but the output stream will always be a KTable. This allows Kafka Streams to update an aggregate value upon the late arrival of further records after the value was produced and emitted. When such late arrival happens, the aggregating KStream or KTable emits a new aggregate value. Because the output is a KTable, the new value is considered to overwrite the old value w [...]
+    </p>
+
+    <h3> <a id="streams_concepts_windowing" href="#streams_concepts_windowing">Windowing</a></h3>
+    <p>
+        Windowing lets you control how to <em>group records that have the same key</em> for stateful operations such as <code>aggregations</code> or <code>joins</code> into so-called <em>windows</em>. Windows are tracked per record key.
+    </p>
+    <p>
+        <code>Windowing operations</code> are available in the <code>Kafka Streams DSL</code>. When working with windows, you can specify a <strong>retention period</strong> for the window. This retention period controls how long Kafka Streams will wait for <strong>out-of-order</strong> or <strong>late-arriving</strong> data records for a given window. If a record arrives after the retention period of a window has passed, the record is discarded and will not be processed in that window.
+    </p>
+    <p>
+        Late-arriving records are always possible in the real world and should be properly accounted for in your applications. It depends on the effective <code>time semantics </code> how late records are handled. In the case of processing-time, the semantics are &quot;when the record is being processed&quot;, which means that the notion of late records is not applicable as, by definition, no record can be late. Hence, late-arriving records can only be considered as such (i.e. as arrivin [...]
+    </p>
+
+    <h3><a id="streams_concepts_duality" href="#streams-concepts-duality">Duality of Streams and Tables</a></h3>
+    <p>
+        When implementing stream processing use cases in practice, you typically need both <strong>streams</strong> and also <strong>databases</strong>.
+        An example use case that is very common in practice is an e-commerce application that enriches an incoming <em>stream</em> of customer
+        transactions with the latest customer information from a <em>database table</em>. In other words, streams are everywhere, but databases are everywhere, too.
+    </p>
+
+    <p>
+        Any stream processing technology must therefore provide <strong>first-class support for streams and tables</strong>.
+        Kafka's Streams API provides such functionality through its core abstractions for
+        <code class="interpreted-text" data-role="ref">streams &lt;streams_concepts_kstream&gt;</code> and
+        <code class="interpreted-text" data-role="ref">tables &lt;streams_concepts_ktable&gt;</code>, which we will talk about in a minute.
+        Now, an interesting observation is that there is actually a <strong>close relationship between streams and tables</strong>,
+        the so-called stream-table duality.
+        And Kafka exploits this duality in many ways: for example, to make your applications
+        <code class="interpreted-text" data-role="ref">elastic &lt;streams_developer-guide_execution-scaling&gt;</code>,
+        to support <code class="interpreted-text" data-role="ref">fault-tolerant stateful processing &lt;streams_developer-guide_state-store_fault-tolerance&gt;</code>,
+        or to run <code class="interpreted-text" data-role="ref">interactive queries &lt;streams_concepts_interactive-queries&gt;</code>
+        against your application's latest processing results. And, beyond its internal usage, the Kafka Streams API
+        also allows developers to exploit this duality in their own applications.
+    </p>
+
+    <p>
+        Before we discuss concepts such as <code class="interpreted-text" data-role="ref">aggregations &lt;streams_concepts_aggregations&gt;</code>
+        in Kafka Streams we must first introduce <strong>tables</strong> in more detail, and talk about the aforementioned stream-table duality.
+        Essentially, this duality means that a stream can be viewed as a table, and a table can be viewed as a stream.
+    </p>
+
+    <h3><a id="streams_state" href="#streams_state">States</a></h3>
+
+    <p>
+        Some stream processing applications don't require state, which means the processing of a message is independent from
+        the processing of all other messages.
+        However, being able to maintain state opens up many possibilities for sophisticated stream processing applications: you
+        can join input streams, or group and aggregate data records. Many such stateful operators are provided by the <a href="/{{version}}/documentation/streams/developer-guide#streams_dsl"><b>Kafka Streams DSL</b></a>.
+    </p>
+    <p>
+        Kafka Streams provides so-called <b>state stores</b>, which can be used by stream processing applications to store and query data.
+        This is an important capability when implementing stateful operations.
+        Every task in Kafka Streams embeds one or more state stores that can be accessed via APIs to store and query data required for processing.
+        These state stores can either be a persistent key-value store, an in-memory hashmap, or another convenient data structure.
+        Kafka Streams offers fault-tolerance and automatic recovery for local state stores.
+    </p>
+    <p>
+        Kafka Streams allows direct read-only queries of the state stores by methods, threads, processes or applications external to the stream processing application that created the state stores. This is provided through a feature called <b>Interactive Queries</b>. All stores are named and Interactive Queries exposes only the read operations of the underlying implementation.
+    </p>
+    <br>
+
+    <h2><a id="streams_processing_guarantee" href="#streams_processing_guarantee">Processing Guarantees</a></h2>
+
+    <p>
+        In stream processing, one of the most frequently asked question is "does my stream processing system guarantee that each record is processed once and only once, even if some failures are encountered in the middle of processing?"
+        Failing to guarantee exactly-once stream processing is a deal-breaker for many applications that cannot tolerate any data-loss or data duplicates, and in that case a batch-oriented framework is usually used in addition
+        to the stream processing pipeline, known as the <a href="http://lambda-architecture.net/">Lambda Architecture</a>.
+        Prior to 0.11.0.0, Kafka only provides at-least-once delivery guarantees and hence any stream processing systems that leverage it as the backend storage could not guarantee end-to-end exactly-once semantics.
+        In fact, even for those stream processing systems that claim to support exactly-once processing, as long as they are reading from / writing to Kafka as the source / sink, their applications cannot actually guarantee that
+        no duplicates will be generated throughout the pipeline.
+
+        Since the 0.11.0.0 release, Kafka has added support to allow its producers to send messages to different topic partitions in a <a href="https://kafka.apache.org/documentation/#semantics">transactional and idempotent manner</a>,
+        and Kafka Streams has hence added the end-to-end exactly-once processing semantics by leveraging these features.
+        More specifically, it guarantees that for any record read from the source Kafka topics, its processing results will be reflected exactly once in the output Kafka topic as well as in the state stores for stateful operations.
+        Note the key difference between Kafka Streams end-to-end exactly-once guarantee with other stream processing frameworks' claimed guarantees is that Kafka Streams tightly integrates with the underlying Kafka storage system and ensure that
+        commits on the input topic offsets, updates on the state stores, and writes to the output topics will be completed atomically instead of treating Kafka as an external system that may have side-effects.
+        To read more details on how this is done inside Kafka Streams, readers are recommended to read <a href="https://cwiki.apache.org/confluence/display/KAFKA/KIP-129%3A+Streams+Exactly-Once+Semantics">KIP-129</a>.
+
+        In order to achieve exactly-once semantics when running Kafka Streams applications, users can simply set the <code>processing.guarantee</code> config value to <b>exactly_once</b> (default value is <b>at_least_once</b>).
+        More details can be found in the <a href="/{{version}}/documentation#streamsconfigs"><b>Kafka Streams Configs</b></a> section.
+    </p>
+
+    <h3><a id="streams_out_of_ordering" href="#streams_out_of_ordering">Out-of-Order Handling</a></h3>
+
+    <p>
+        Besides the guarantee that each record will be processed exactly-once, another issue that many stream processing application will face is how to
+        handle <a href="tbd">out-of-order data</a> that may impact their business logic. In Kafka Streams, there are two causes that could potentially
+        result in out-of-order data arrivals with respect to their timestamps:
+    </p>
+
+    <ul>
+        <li> Within a topic-partition, a record's timestamp may not be monotonically increasing along with their offsets. Since Kafka Streams will always try to process records within a topic-partition to follow the offset order,
+            it can cause records with larger timestamps (but smaller offsets) to be processed earlier than records with smaller timestamps (but larger offsets) in the same topic-partition.
+        </li>
+        <li> Within a <a href="/{{version}}/documentation/streams/architecture#streams_architecture_tasks">stream task</a> that may be processing multiple topic-partitions, if users configure the application to not wait for all partitions to contain some buffered data and
+             pick from the partition with the smallest timestamp to process the next record, then later on when some records are fetched for other topic-partitions, their timestamps may be smaller than those processed records fetched from another topic-partition.
+        </li>
+    </ul>
+
+    <p>
+        For stateless operations, out-of-order data will not impact processing logic since only one record is considered at a time, without looking into the history of past processed records;
+        for stateful operations such as aggregations and joins, however, out-of-order data could cause the processing logic to be incorrect. If users want to handle such out-of-order data, generally they need to allow their applications
+        to wait for longer time while bookkeeping their states during the wait time, i.e. making trade-off decisions between latency, cost, and correctness.
+        In Kafka Streams specifically, users can configure their window operators for windowed aggregations to achieve such trade-offs (details can be found in <a href="/{{version}}/documentation/streams/developer-guide"><b>Developer Guide</b></a>).
+        As for Joins, users have to be aware that some of the out-of-order data cannot be handled by increasing on latency and cost in Streams yet:
+    </p>
+
+    <ul>
+        <li> For Stream-Stream joins, all three types (inner, outer, left) handle out-of-order records correctly, but the resulted stream may contain unnecessary leftRecord-null for left joins, and leftRecord-null or null-rightRecord for outer joins. </li>
+        <li> For Stream-Table joins, out-of-order records are not handled (i.e., Streams applications don't check for out-of-order records and just process all records in offset order), and hence it may produce unpredictable results. </li>
+        <li> For Table-Table joins, out-of-order records are not handled (i.e., Streams applications don't check for out-of-order records and just process all records in offset order). However, the join result is a changelog stream and hence will be eventually consistent. </li>
+    </ul>
+
+    <div class="pagination">
+        <a href="/{{version}}/documentation/streams/tutorial" class="pagination__btn pagination__btn__prev">Previous</a>
+        <a href="/{{version}}/documentation/streams/architecture" class="pagination__btn pagination__btn__next">Next</a>
+    </div>
+</script>
+
+<!--#include virtual="../../includes/_header.htm" -->
+<!--#include virtual="../../includes/_top.htm" -->
+<div class="content documentation documentation--current">
+    <!--#include virtual="../../includes/_nav.htm" -->
+    <div class="right">
+        <!--#include virtual="../../includes/_docs_banner.htm" -->
+        <ul class="breadcrumbs">
+            <li><a href="/documentation">Documentation</a></li>
+            <li><a href="/documentation/streams">Kafka Streams</a></li>
+        </ul>
+        <div class="p-content"></div>
+    </div>
+</div>
+<!--#include virtual="../../includes/_footer.htm" -->
+<script>
+$(function() {
+  // Show selected style on nav item
+  $('.b-nav__streams').addClass('selected');
+
+
+     //sticky secondary nav
+          var $navbar = $(".sub-nav-sticky"),
+               y_pos = $navbar.offset().top,
+               height = $navbar.height();
+       
+           $(window).scroll(function() {
+               var scrollTop = $(window).scrollTop();
+           
+               if (scrollTop > y_pos - height) {
+                   $navbar.addClass("navbar-fixed")
+               } else if (scrollTop <= y_pos) {
+                   $navbar.removeClass("navbar-fixed")
+               }
+           });
+
+  // Display docs subnav items
+  $('.b-nav__docs').parent().toggleClass('nav__item__with__subs--expanded');
+});
+</script>
diff --git a/21/streams/developer-guide/app-reset-tool.html b/21/streams/developer-guide/app-reset-tool.html
new file mode 100644
index 0000000..2f82cf1
--- /dev/null
+++ b/21/streams/developer-guide/app-reset-tool.html
@@ -0,0 +1,196 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<script><!--#include virtual="../../js/templateData.js" --></script>
+
+<script id="content-template" type="text/x-handlebars-template">
+  <!-- h1>Developer Guide for Kafka Streams</h1 -->
+  <div class="sub-nav-sticky">
+    <div class="sticky-top">
+      <!-- div style="height:35px">
+        <a href="/{{version}}/documentation/streams/">Introduction</a>
+        <a class="active-menu-item" href="/{{version}}/documentation/streams/developer-guide">Developer Guide</a>
+        <a href="/{{version}}/documentation/streams/core-concepts">Concepts</a>
+        <a href="/{{version}}/documentation/streams/quickstart">Run Demo App</a>
+        <a href="/{{version}}/documentation/streams/tutorial">Tutorial: Write App</a>
+      </div -->
+    </div>
+  </div>
+
+    <div class="section" id="application-reset-tool">
+        <span id="streams-developer-guide-app-reset"></span><h1>Application Reset Tool<a class="headerlink" href="#application-reset-tool" title="Permalink to this headline"></a></h1>
+        <p>You can reset an application and force it to reprocess its data from scratch by using the application reset tool.
+            This can be useful for development and testing, or when fixing bugs.</p>
+        <p>The application reset tool handles the Kafka Streams <a class="reference internal" href="manage-topics.html#streams-developer-guide-topics-user"><span class="std std-ref">user topics</span></a> (input,
+            output, and intermediate topics) and <a class="reference internal" href="manage-topics.html#streams-developer-guide-topics-internal"><span class="std std-ref">internal topics</span></a> differently
+            when resetting the application.</p>
+        <p>Here&#8217;s what the application reset tool does for each topic type:</p>
+        <ul class="simple">
+            <li>Input topics: Reset offsets to specified position (by default to the beginning of the topic).</li>
+            <li>Intermediate topics: Skip to the end of the topic, i.e., set the application&#8217;s committed consumer offsets for all partitions to each partition&#8217;s <code class="docutils literal"><span class="pre">logSize</span></code> (for consumer group <code class="docutils literal"><span class="pre">application.id</span></code>).</li>
+            <li>Internal topics: Delete the internal topic (this automatically deletes any committed offsets).</li>
+        </ul>
+        <p>The application reset tool does not:</p>
+        <ul class="simple">
+            <li>Reset output topics of an application. If any output (or intermediate) topics are consumed by downstream
+                applications, it is your responsibility to adjust those downstream applications as appropriate when you reset the
+                upstream application.</li>
+            <li>Reset the local environment of your application instances.  It is your responsibility to delete the local
+                state on any machine on which an application instance was run.  See the instructions in section
+                <a class="reference internal" href="#streams-developer-guide-reset-local-environment"><span class="std std-ref">Step 2: Reset the local environments of your application instances</span></a> on how to do this.</li>
+        </ul>
+        <dl class="docutils">
+            <dt>Prerequisites</dt>
+            <dd><ul class="first last">
+                <li><p class="first">All instances of your application must be stopped. Otherwise, the application may enter an invalid state, crash, or produce incorrect results. You can verify whether the consumer group with ID <code class="docutils literal"><span class="pre">application.id</span></code> is still active by using <code class="docutils literal"><span class="pre">bin/kafka-consumer-groups</span></code>.</p>
+                </li>
+                <li><p class="first">Use this tool with care and double-check its parameters: If you provide wrong parameter values (e.g., typos in <code class="docutils literal"><span class="pre">application.id</span></code>) or specify parameters inconsistently (e.g., specify the wrong input topics for the application), this tool might invalidate the application&#8217;s state or even impact other applications, consumer groups, or your Kafka topics.</p>
+                </li>
+                <li><p class="first">You should manually delete and re-create any intermediate topics before running the application reset tool. This will free up disk space in Kafka brokers.</p>
+                </li>
+                <li><p class="first">You should delete and recreate intermediate topics before running the application reset tool, unless the following applies:</p>
+                    <blockquote>
+                        <div><ul class="simple">
+                            <li>You have external downstream consumers for the application&#8217;s intermediate topics.</li>
+                            <li>You are in a development environment where manually deleting and re-creating intermediate topics is unnecessary.</li>
+                        </ul>
+                        </div></blockquote>
+                </li>
+            </ul>
+            </dd>
+        </dl>
+        <div class="section" id="step-1-run-the-application-reset-tool">
+            <h2>Step 1: Run the application reset tool<a class="headerlink" href="#step-1-run-the-application-reset-tool" title="Permalink to this headline"></a></h2>
+            <p>Invoke the application reset tool from the command line</p>
+            <div class="highlight-bash"><div class="highlight"><pre><span></span>&lt;path-to-kafka&gt;/bin/kafka-streams-application-reset
+</pre></div>
+            </div>
+            <p>The tool accepts the following parameters:</p>
+            <div class="highlight-bash"><div class="highlight"><pre><span></span>Option <span class="o">(</span>* <span class="o">=</span> required<span class="o">)</span>                 Description
+---------------------                 -----------
+* --application-id &lt;String: id&gt;       The Kafka Streams application ID
+                                        <span class="o">(</span>application.id<span class="o">)</span>.
+--bootstrap-servers &lt;String: urls&gt;    Comma-separated list of broker urls with
+                                        format: HOST1:PORT1,HOST2:PORT2
+                                        <span class="o">(</span>default: localhost:9092<span class="o">)</span>
+--by-duration &lt;String: urls&gt;      Reset offsets to offset by duration from
+                                        current timestamp. Format: '<span>PnDTnHnMnS</span>'
+--config-file &lt;String: file name&gt;     Property file containing configs to be
+                                        passed to admin clients and embedded
+                                        consumer.
+--dry-run                             Display the actions that would be
+                                        performed without executing the reset
+                                        commands.
+--from-file &lt;String: urls&gt;        Reset offsets to values defined in CSV
+                                        file.
+--input-topics &lt;String: list&gt;         Comma-separated list of user input
+                                        topics. For these topics, the tool will
+                                        reset the offset to the earliest
+                                        available offset.
+--intermediate-topics &lt;String: list&gt;  Comma-separated list of intermediate user
+                                        topics <span class="o">(</span>topics used in the through<span class="o">()</span>
+                                        method<span class="o">)</span>. For these topics, the tool
+                                        will skip to the end.
+--shift-by &lt;Long: number-of-offsets&gt; Reset offsets shifting current offset by
+                                        'n', where 'n' can be positive or
+                                        negative
+--to-datetime &lt;String&gt;                Reset offsets to offset from datetime.
+                                        Format: 'YYYY-MM-DDTHH:mm:SS.sss'
+--to-earliest                         Reset offsets to earliest offset.
+--to-latest                           Reset offsets to latest offset.
+--to-offset &lt;Long&gt;                    Reset offsets to a specific offset.
+--zookeeper                           Zookeeper option is deprecated by
+                                        bootstrap.servers, as the reset tool
+                                        would no longer access Zookeeper
+                                        directly.
+</pre></div>
+            </div>
+            <p>Consider the following as reset-offset scenarios for <code>input-topics</code>:</p>
+            <ul>
+                <li> by-duration</li>
+                <li> from-file</li>
+                <li> shift-by</li>
+                <li> to-datetime</li>
+                <li> to-earliest</li>
+                <li> to-latest</li>
+                <li> to-offset</li>
+            </ul>
+            <p>Only one of these scenarios can be defined. If not, <code>to-earliest</code> will be executed by default</p>
+            <p>All the other parameters can be combined as needed.  For example, if you want to restart an application from an
+                empty internal state, but not reprocess previous data, simply omit the parameters <code class="docutils literal"><span class="pre">--input-topics</span></code> and
+                <code class="docutils literal"><span class="pre">--intermediate-topics</span></code>.</p>
+        </div>
+        <div class="section" id="step-2-reset-the-local-environments-of-your-application-instances">
+            <span id="streams-developer-guide-reset-local-environment"></span><h2>Step 2: Reset the local environments of your application instances<a class="headerlink" href="#step-2-reset-the-local-environments-of-your-application-instances" title="Permalink to this headline"></a></h2>
+            <p>For a complete application reset, you must delete the application&#8217;s local state directory on any machines where the
+                application instance was run. You must do this before restarting an application instance on the same machine.  You can
+                use either of these methods:</p>
+            <ul class="simple">
+                <li>The API method <code class="docutils literal"><span class="pre">KafkaStreams#cleanUp()</span></code> in your application code.</li>
+                <li>Manually delete the corresponding local state directory (default location: <code class="docutils literal"><span class="pre">/tmp/kafka-streams/&lt;application.id&gt;</span></code>). For more information, see <a href="../../../javadoc/org/apache/kafka/streams/StreamsConfig.html#STATE_DIR_CONFIG">Streams</a> javadocs.</li>
+            </ul>
+</div>
+</div>
+
+
+               </div>
+              </div>
+  <div class="pagination">
+    <a href="/{{version}}/documentation/streams/developer-guide/security" class="pagination__btn pagination__btn__prev">Previous</a>
+      <a href="/{{version}}/documentation/streams/upgrade-guide" class="pagination__btn pagination__btn__next">Next</a>
+  </div>
+</script>
+
+<!--#include virtual="../../../includes/_header.htm" -->
+<!--#include virtual="../../../includes/_top.htm" -->
+<div class="content documentation documentation--current">
+  <!--#include virtual="../../../includes/_nav.htm" -->
+  <div class="right">
+    <!--#include virtual="../../../includes/_docs_banner.htm" -->
+    <ul class="breadcrumbs">
+      <li><a href="/documentation">Documentation</a></li>
+      <li><a href="/documentation/streams">Kafka Streams</a></li>
+      <li><a href="/documentation/streams/developer-guide/">Developer Guide</a></li>
+    </ul>
+    <div class="p-content"></div>
+  </div>
+</div>
+<!--#include virtual="../../../includes/_footer.htm" -->
+<script>
+    $(function() {
+        // Show selected style on nav item
+        $('.b-nav__streams').addClass('selected');
+
+        //sticky secondary nav
+        var $navbar = $(".sub-nav-sticky"),
+            y_pos = $navbar.offset().top,
+            height = $navbar.height();
+
+        $(window).scroll(function() {
+            var scrollTop = $(window).scrollTop();
+
+            if (scrollTop > y_pos - height) {
+                $navbar.addClass("navbar-fixed")
+            } else if (scrollTop <= y_pos) {
+                $navbar.removeClass("navbar-fixed")
+            }
+        });
+
+        // Display docs subnav items
+        $('.b-nav__docs').parent().toggleClass('nav__item__with__subs--expanded');
+    });
+</script>
\ No newline at end of file
diff --git a/21/streams/developer-guide/config-streams.html b/21/streams/developer-guide/config-streams.html
new file mode 100644
index 0000000..a226517
--- /dev/null
+++ b/21/streams/developer-guide/config-streams.html
@@ -0,0 +1,825 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<script><!--#include virtual="../../js/templateData.js" --></script>
+
+<script id="content-template" type="text/x-handlebars-template">
+  <!-- h1>Developer Guide for Kafka Streams</h1 -->
+  <div class="sub-nav-sticky">
+    <div class="sticky-top">
+      <!-- div style="height:35px">
+        <a href="/{{version}}/documentation/streams/">Introduction</a>
+        <a class="active-menu-item" href="/{{version}}/documentation/streams/developer-guide">Developer Guide</a>
+        <a href="/{{version}}/documentation/streams/core-concepts">Concepts</a>
+        <a href="/{{version}}/documentation/streams/quickstart">Run Demo App</a>
+        <a href="/{{version}}/documentation/streams/tutorial">Tutorial: Write App</a>
+      </div -->
+    </div>
+  </div>
+
+
+  <div class="section" id="configuring-a-streams-application">
+    <span id="streams-developer-guide-configuration"></span><h1>Configuring a Streams Application<a class="headerlink" href="#configuring-a-streams-application" title="Permalink to this headline"></a></h1>
+    <p>Kafka and Kafka Streams configuration options must be configured before using Streams. You can configure Kafka Streams by specifying parameters in a <code class="docutils literal"><span class="pre">java.util.Properties</span></code> instance.</p>
+    <ol class="arabic">
+      <li><p class="first">Create a <code class="docutils literal"><span class="pre">java.util.Properties</span></code> instance.</p>
+      </li>
+      <li><p class="first">Set the <a class="reference internal" href="#streams-developer-guide-required-configs"><span class="std std-ref">parameters</span></a>. For example:</p>
+        <div class="highlight-java"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">java.util.Properties</span><span class="o">;</span>
+<span class="kn">import</span> <span class="nn">org.apache.kafka.streams.StreamsConfig</span><span class="o">;</span>
+
+<span class="n">Properties</span> <span class="n">settings</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Properties</span><span class="o">();</span>
+<span class="c1">// Set a few key parameters</span>
+<span class="n">settings</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">StreamsConfig</span><span class="o">.</span><span class="na">APPLICATION_ID_CONFIG</span><span class="o">,</span> <span class="s">&quot;my-first-streams-application&quot;</span><span class="o">);</span>
+<span class="n">settings</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">StreamsConfig</span><span class="o">.</span><span class="na">BOOTSTRAP_SERVERS_CONFIG</span><span class="o">,</span> <span class="s">&quot;kafka-broker1:9092&quot;</span><span class="o">);</span>
+<span class="c1">// Any further settings</span>
+<span class="n">settings</span><span class="o">.</span><span class="na">put</span><span class="o">(...</span> <span class="o">,</span> <span class="o">...);</span>
+</pre></div>
+        </div>
+      </li>
+    </ol>
+    <div class="section" id="configuration-parameter-reference">
+      <span id="streams-developer-guide-required-configs"></span><h2>Configuration parameter reference<a class="headerlink" href="#configuration-parameter-reference" title="Permalink to this headline"></a></h2>
+      <p>This section contains the most common Streams configuration parameters. For a full reference, see the <a class="reference external" href="../../../javadoc/org/apache/kafka/streams/StreamsConfig.html">Streams</a> Javadocs.</p>
+      <div class="contents local topic" id="contents">
+        <ul class="simple">
+          <li><a class="reference internal" href="#required-configuration-parameters" id="id3">Required configuration parameters</a><ul>
+            <li><a class="reference internal" href="#application-id" id="id4">application.id</a></li>
+            <li><a class="reference internal" href="#bootstrap-servers" id="id5">bootstrap.servers</a></li>
+          </ul>
+          </li>
+          <li><a class="reference internal" href="#optional-configuration-parameters" id="id6">Optional configuration parameters</a><ul>
+            <li><a class="reference internal" href="#default-deserialization-exception-handler" id="id7">default.deserialization.exception.handler</a></li>
+            <li><a class="reference internal" href="#default-production-exception-handler" id="id24">default.production.exception.handler</a></li>
+            <li><a class="reference internal" href="#default-key-serde" id="id8">default.key.serde</a></li>
+            <li><a class="reference internal" href="#default-value-serde" id="id9">default.value.serde</a></li>
+            <li><a class="reference internal" href="#num-standby-replicas" id="id10">num.standby.replicas</a></li>
+            <li><a class="reference internal" href="#num-stream-threads" id="id11">num.stream.threads</a></li>
+            <li><a class="reference internal" href="#partition-grouper" id="id12">partition.grouper</a></li>
+            <li><a class="reference internal" href="#processing-guarantee" id="id25">processing.guarantee</a></li>
+            <li><a class="reference internal" href="#replication-factor" id="id13">replication.factor</a></li>
+            <li><a class="reference internal" href="#state-dir" id="id14">state.dir</a></li>
+            <li><a class="reference internal" href="#timestamp-extractor" id="id15">timestamp.extractor</a></li>
+          </ul>
+          </li>
+          <li><a class="reference internal" href="#kafka-consumers-and-producer-configuration-parameters" id="id16">Kafka consumers and producer configuration parameters</a><ul>
+            <li><a class="reference internal" href="#naming" id="id17">Naming</a></li>
+            <li><a class="reference internal" href="#default-values" id="id18">Default Values</a></li>
+            <li><a class="reference internal" href="#enable-auto-commit" id="id19">enable.auto.commit</a></li>
+            <li><a class="reference internal" href="#rocksdb-config-setter" id="id20">rocksdb.config.setter</a></li>
+          </ul>
+          </li>
+          <li><a class="reference internal" href="#recommended-configuration-parameters-for-resiliency" id="id21">Recommended configuration parameters for resiliency</a><ul>
+            <li><a class="reference internal" href="#acks" id="id22">acks</a></li>
+            <li><a class="reference internal" href="#id2" id="id23">replication.factor</a></li>
+          </ul>
+          </li>
+        </ul>
+      </div>
+      <div class="section" id="required-configuration-parameters">
+        <h3><a class="toc-backref" href="#id3">Required configuration parameters</a><a class="headerlink" href="#required-configuration-parameters" title="Permalink to this headline"></a></h3>
+        <p>Here are the required Streams configuration parameters.</p>
+        <table border="1" class="non-scrolling-table docutils">
+          <thead valign="bottom">
+          <tr class="row-odd"><th class="head">Parameter Name</th>
+            <th class="head">Importance</th>
+            <th class="head" colspan="2">Description</th>
+            <th class="head">Default Value</th>
+          </tr>
+          </thead>
+          <tbody valign="top">
+          <tr class="row-even"><td>application.id</td>
+            <td>Required</td>
+            <td colspan="2">An identifier for the stream processing application.  Must be unique within the Kafka cluster.</td>
+            <td>None</td>
+          </tr>
+          <tr class="row-odd"><td>bootstrap.servers</td>
+            <td>Required</td>
+            <td colspan="2">A list of host/port pairs to use for establishing the initial connection to the Kafka cluster.</td>
+            <td>None</td>
+          </tr>
+          </tbody>
+        </table>
+        <div class="section" id="application-id">
+          <h4><a class="toc-backref" href="#id4">application.id</a><a class="headerlink" href="#application-id" title="Permalink to this headline"></a></h4>
+          <blockquote>
+            <div><p>(Required) The application ID. Each stream processing application must have a unique ID. The same ID must be given to
+              all instances of the application.  It is recommended to use only alphanumeric characters, <code class="docutils literal"><span class="pre">.</span></code> (dot), <code class="docutils literal"><span class="pre">-</span></code> (hyphen), and <code class="docutils literal"><span class="pre">_</span></code> (underscore). Examples: <code class="docutils literal"><span class="pre">&quot;hello_world&quot;</span></code>, <code class="docutils literal"><span class="pre">&quot;hello [...]
+              <p>This ID is used in the following places to isolate resources used by the application from others:</p>
+              <ul class="simple">
+                <li>As the default Kafka consumer and producer <code class="docutils literal"><span class="pre">client.id</span></code> prefix</li>
+                <li>As the Kafka consumer <code class="docutils literal"><span class="pre">group.id</span></code> for coordination</li>
+                <li>As the name of the subdirectory in the state directory (cf. <code class="docutils literal"><span class="pre">state.dir</span></code>)</li>
+                <li>As the prefix of internal Kafka topic names</li>
+              </ul>
+              <dl class="docutils">
+                <dt>Tip:</dt>
+                <dd>When an application is updated, the <code class="docutils literal"><span class="pre">application.id</span></code> should be changed unless you want to reuse the existing data in internal topics and state stores.
+                  For example, you could embed the version information within <code class="docutils literal"><span class="pre">application.id</span></code>, as <code class="docutils literal"><span class="pre">my-app-v1.0.0</span></code> and <code class="docutils literal"><span class="pre">my-app-v1.0.2</span></code>.</dd>
+              </dl>
+            </div></blockquote>
+        </div>
+        <div class="section" id="bootstrap-servers">
+          <h4><a class="toc-backref" href="#id5">bootstrap.servers</a><a class="headerlink" href="#bootstrap-servers" title="Permalink to this headline"></a></h4>
+          <blockquote>
+            <div><p>(Required) The Kafka bootstrap servers. This is the same <a class="reference external" href="http://kafka.apache.org/documentation.html#producerconfigs">setting</a> that is used by the underlying producer and consumer clients to connect to the Kafka cluster.
+              Example: <code class="docutils literal"><span class="pre">&quot;kafka-broker1:9092,kafka-broker2:9092&quot;</span></code>.</p>
+              <dl class="docutils">
+                <dt>Tip:</dt>
+                <dd>Kafka Streams applications can only communicate with a single Kafka cluster specified by this config value.
+                  Future versions of Kafka Streams will support connecting to different Kafka clusters for reading input
+                  streams and writing output streams.</dd>
+              </dl>
+            </div></blockquote>
+        </div>
+      </div>
+      <div class="section" id="optional-configuration-parameters">
+        <span id="streams-developer-guide-optional-configs"></span><h3><a class="toc-backref" href="#id6">Optional configuration parameters</a><a class="headerlink" href="#optional-configuration-parameters" title="Permalink to this headline"></a></h3>
+        <p>Here are the optional <a href="../../../javadoc/org/apache/kafka/streams/StreamsConfig.html">Streams</a> javadocs, sorted by level of importance:</p>
+        <blockquote>
+          <div><ul class="simple">
+            <li>High: These parameters can have a significant impact on performance. Take care when deciding the values of these parameters.</li>
+            <li>Medium: These parameters can have some impact on performance. Your specific environment will determine how much tuning effort should be focused on these parameters.</li>
+            <li>Low: These parameters have a less general or less significant impact on performance.</li>
+          </ul>
+          </div></blockquote>
+        <table border="1" class="non-scrolling-table docutils">
+          <thead valign="bottom">
+          <tr class="row-odd"><th class="head">Parameter Name</th>
+            <th class="head">Importance</th>
+            <th class="head" colspan="2">Description</th>
+            <th class="head">Default Value</th>
+          </tr>
+          </thead>
+          <tbody valign="top">
+          <tr class="row-even"><td>application.server</td>
+            <td>Low</td>
+            <td colspan="2">A host:port pair pointing to an embedded user defined endpoint that can be used for discovering the locations of
+              state stores within a single Kafka Streams application. The value of this must be different for each instance
+              of the application.</td>
+            <td>the empty string</td>
+          </tr>
+          <tr class="row-odd"><td>buffered.records.per.partition</td>
+            <td>Low</td>
+            <td colspan="2">The maximum number of records to buffer per partition.</td>
+            <td>1000</td>
+          </tr>
+          <tr class="row-even"><td>cache.max.bytes.buffering</td>
+            <td>Medium</td>
+            <td colspan="2">Maximum number of memory bytes to be used for record caches across all threads.</td>
+            <td>10485760 bytes</td>
+          </tr>
+          <tr class="row-odd"><td>client.id</td>
+            <td>Medium</td>
+            <td colspan="2">An ID string to pass to the server when making requests.
+              (This setting is passed to the consumer/producer clients used internally by Kafka Streams.)</td>
+            <td>the empty string</td>
+          </tr>
+          <tr class="row-even"><td>commit.interval.ms</td>
+            <td>Low</td>
+            <td colspan="2">The frequency with which to save the position (offsets in source topics) of tasks.</td>
+            <td>30000 milliseconds</td>
+          </tr>
+          <tr class="row-odd"><td>default.deserialization.exception.handler</td>
+            <td>Medium</td>
+            <td colspan="2">Exception handling class that implements the <code class="docutils literal"><span class="pre">DeserializationExceptionHandler</span></code> interface.</td>
+            <td><code class="docutils literal"><span class="pre">LogAndContinueExceptionHandler</span></code></td>
+          </tr>
+          <tr class="row-even"><td>default.production.exception.handler</td>
+            <td>Medium</td>
+            <td colspan="2">Exception handling class that implements the <code class="docutils literal"><span class="pre">ProductionExceptionHandler</span></code> interface.</td>
+            <td><code class="docutils literal"><span class="pre">DefaultProductionExceptionHandler</span></code></td>
+          </tr>
+          <tr class="row-odd"><td>key.serde</td>
+            <td>Medium</td>
+            <td colspan="2">Default serializer/deserializer class for record keys, implements the <code class="docutils literal"><span class="pre">Serde</span></code> interface (see also value.serde).</td>
+            <td><code class="docutils literal"><span class="pre">Serdes.ByteArray().getClass().getName()</span></code></td>
+          </tr>
+          <tr class="row-even"><td>metric.reporters</td>
+            <td>Low</td>
+            <td colspan="2">A list of classes to use as metrics reporters.</td>
+            <td>the empty list</td>
+          </tr>
+          <tr class="row-odd"><td>metrics.num.samples</td>
+            <td>Low</td>
+            <td colspan="2">The number of samples maintained to compute metrics.</td>
+            <td>2</td>
+          </tr>
+          <tr class="row-even"><td>metrics.recording.level</td>
+            <td>Low</td>
+            <td colspan="2">The highest recording level for metrics.</td>
+            <td><code class="docutils literal"><span class="pre">INFO</span></code></td>
+          </tr>
+          <tr class="row-odd"><td>metrics.sample.window.ms</td>
+            <td>Low</td>
+            <td colspan="2">The window of time a metrics sample is computed over.</td>
+            <td>30000 milliseconds</td>
+          </tr>
+          <tr class="row-even"><td>num.standby.replicas</td>
+            <td>Medium</td>
+            <td colspan="2">The number of standby replicas for each task.</td>
+            <td>0</td>
+          </tr>
+          <tr class="row-odd"><td>num.stream.threads</td>
+            <td>Medium</td>
+            <td colspan="2">The number of threads to execute stream processing.</td>
+            <td>1</td>
+          </tr>
+          <tr class="row-even"><td>partition.grouper</td>
+            <td>Low</td>
+            <td colspan="2">Partition grouper class that implements the <code class="docutils literal"><span class="pre">PartitionGrouper</span></code> interface.</td>
+            <td>See <a class="reference internal" href="#streams-developer-guide-partition-grouper"><span class="std std-ref">Partition Grouper</span></a></td>
+          </tr>
+          <tr class="row-even"><td>processing.guarantee</td>
+            <td>Low</td>
+            <td colspan="2">The processing mode. Can be either <code class="docutils literal"><span class="pre">"at_least_once"</span></code> (default) or <code class="docutils literal"><span class="pre">"exactly_once"</span></code>.
+            <td>See <a class="reference internal" href="#streams-developer-guide-processing-guarantedd"><span class="std std-ref">Processing Guarantee</span></a></td>
+          </tr>
+          <tr class="row-odd"><td>poll.ms</td>
+            <td>Low</td>
+            <td colspan="2">The amount of time in milliseconds to block waiting for input.</td>
+            <td>100 milliseconds</td>
+          </tr>
+          <tr class="row-even"><td>replication.factor</td>
+            <td>High</td>
+            <td colspan="2">The replication factor for changelog topics and repartition topics created by the application.</td>
+            <td>1</td>
+          </tr>
+          <tr class="row-even"><td>retries</td>
+              <td>Medium</td>
+              <td colspan="2">The number of retries for broker requests that return a retryable error. </td>
+              <td>0</td>
+          </tr>
+          <tr class="row-even"><td>retry.backoff.ms</td>
+              <td>Medium</td>
+              <td colspan="2">The amount of time in milliseconds, before a request is retried. This applies if the <code class="docutils literal"><span class="pre">retries</span></code> parameter is configured to be greater than 0. </td>
+              <td>100</td>
+          </tr>
+          <tr class="row-odd"><td>state.cleanup.delay.ms</td>
+            <td>Low</td>
+            <td colspan="2">The amount of time in milliseconds to wait before deleting state when a partition has migrated.</td>
+            <td>6000000 milliseconds</td>
+          </tr>
+          <tr class="row-even"><td>state.dir</td>
+            <td>High</td>
+            <td colspan="2">Directory location for state stores.</td>
+            <td><code class="docutils literal"><span class="pre">/tmp/kafka-streams</span></code></td>
+          </tr>
+          <tr class="row-odd"><td>timestamp.extractor</td>
+            <td>Medium</td>
+            <td colspan="2">Timestamp extractor class that implements the <code class="docutils literal"><span class="pre">TimestampExtractor</span></code> interface.</td>
+            <td>See <a class="reference internal" href="#streams-developer-guide-timestamp-extractor"><span class="std std-ref">Timestamp Extractor</span></a></td>
+          </tr>
+          <tr class="row-even"><td>value.serde</td>
+            <td>Medium</td>
+            <td colspan="2">Default serializer/deserializer class for record values, implements the <code class="docutils literal"><span class="pre">Serde</span></code> interface (see also key.serde).</td>
+            <td><code class="docutils literal"><span class="pre">Serdes.ByteArray().getClass().getName()</span></code></td>
+          </tr>
+          <tr class="row-odd"><td>windowstore.changelog.additional.retention.ms</td>
+            <td>Low</td>
+            <td colspan="2">Added to a windows maintainMs to ensure data is not deleted from the log prematurely. Allows for clock drift.</td>
+            <td>86400000 milliseconds = 1 day</td>
+          </tr>
+          </tbody>
+        </table>
+        <div class="section" id="default-deserialization-exception-handler">
+          <span id="streams-developer-guide-deh"></span><h4><a class="toc-backref" href="#id7">default.deserialization.exception.handler</a><a class="headerlink" href="#default-deserialization-exception-handler" title="Permalink to this headline"></a></h4>
+          <blockquote>
+            <div><p>The default deserialization exception handler allows you to manage record exceptions that fail to deserialize. This
+              can be caused by corrupt data, incorrect serialization logic, or unhandled record types. The implemented exception
+              handler needs to return a <code>FAIL</code> or <code>CONTINUE</code> depending on the record and the exception thrown. Returning
+              <code>FAIL</code> will signal that Streams should shut down and <code>CONTINUE</code> will signal that Streams should ignore the issue
+              and continue processing. The following library built-in exception handlers are available:</p>
+              <ul class="simple">
+                <li><a class="reference external" href="../../../javadoc/org/apache/kafka/streams/errors/LogAndContinueExceptionHandler.html">LogAndContinueExceptionHandler</a>:
+                  This handler logs the deserialization exception and then signals the processing pipeline to continue processing more records.
+                  This log-and-skip strategy allows Kafka Streams to make progress instead of failing if there are records that fail
+                  to deserialize.</li>
+                <li><a class="reference external" href="../../../javadoc/org/apache/kafka/streams/errors/LogAndFailExceptionHandler.html">LogAndFailExceptionHandler</a>.
+                  This handler logs the deserialization exception and then signals the processing pipeline to stop processing more records.</li>
+              </ul>
+
+              <p>You can also provide your own customized exception handler besides the library provided ones to meet your needs. For example, you can choose to forward corrupt
+                records into a quarantine topic (think: a "dead letter queue") for further processing. To do this, use the Producer API to write a corrupted record directly to
+                the quarantine topic. To be more concrete, you can create a separate <code>KafkaProducer</code> object outside the Streams client, and pass in this object
+                as well as the dead letter queue topic name into the <code>Properties</code> map, which then can be retrieved from the <code>configure</code> function call.
+                The drawback of this approach is that "manual" writes are side effects that are invisible to the Kafka Streams runtime library,
+                so they do not benefit from the end-to-end processing guarantees of the Streams API:</p>
+
+              <pre class="brush: java;">
+              public class SendToDeadLetterQueueExceptionHandler implements DeserializationExceptionHandler {
+                  KafkaProducer&lt;byte[], byte[]&gt; dlqProducer;
+                  String dlqTopic;
+
+                  @Override
+                  public DeserializationHandlerResponse handle(final ProcessorContext context,
+                                                               final ConsumerRecord&lt;byte[], byte[]&gt; record,
+                                                               final Exception exception) {
+
+                      log.warn("Exception caught during Deserialization, sending to the dead queue topic; " +
+                          "taskId: {}, topic: {}, partition: {}, offset: {}",
+                          context.taskId(), record.topic(), record.partition(), record.offset(),
+                          exception);
+
+                      dlqProducer.send(new ProducerRecord&lt;&gt;(dlqTopic, record.timestamp(), record.key(), record.value(), record.headers())).get();
+
+                      return DeserializationHandlerResponse.CONTINUE;
+                  }
+
+                  @Override
+                  public void configure(final Map&lt;String, ?&gt; configs) {
+                      dlqProducer = .. // get a producer from the configs map
+                      dlqTopic = .. // get the topic name from the configs map
+                  }
+              }
+              </pre>
+
+            </div></blockquote>
+        </div>
+        <div class="section" id="default-production-exception-handler">
+          <span id="streams-developer-guide-peh"></span><h4><a class="toc-backref" href="#id24">default.production.exception.handler</a><a class="headerlink" href="#default-production-exception-handler" title="Permalink to this headline"></a></h4>
+          <blockquote>
+            <div><p>The default production exception handler allows you to manage exceptions triggered when trying to interact with a broker
+              such as attempting to produce a record that is too large. By default, Kafka provides and uses the <a class="reference external" href="../../../javadoc/org/apache/kafka/streams/errors/DefaultProductionExceptionHandler.html">DefaultProductionExceptionHandler</a>
+              that always fails when these exceptions occur.</p>
+
+            <p>Each exception handler can return a <code>FAIL</code> or <code>CONTINUE</code> depending on the record and the exception thrown. Returning <code>FAIL</code> will signal that Streams should shut down and <code>CONTINUE</code> will signal that Streams
+            should ignore the issue and continue processing. If you want to provide an exception handler that always ignores records that are too large, you could implement something like the following:</p>
+
+            <pre class="brush: java;">
+            import java.util.Properties;
+            import org.apache.kafka.streams.StreamsConfig;
+            import org.apache.kafka.common.errors.RecordTooLargeException;
+            import org.apache.kafka.streams.errors.ProductionExceptionHandler;
+            import org.apache.kafka.streams.errors.ProductionExceptionHandler.ProductionExceptionHandlerResponse;
+
+            public class IgnoreRecordTooLargeHandler implements ProductionExceptionHandler {
+                public void configure(Map&lt;String, Object&gt; config) {}
+
+                public ProductionExceptionHandlerResponse handle(final ProducerRecord&lt;byte[], byte[]&gt; record,
+                                                                 final Exception exception) {
+                    if (exception instanceof RecordTooLargeException) {
+                        return ProductionExceptionHandlerResponse.CONTINUE;
+                    } else {
+                        return ProductionExceptionHandlerResponse.FAIL;
+                    }
+                }
+            }
+
+            Properties settings = new Properties();
+
+            // other various kafka streams settings, e.g. bootstrap servers, application id, etc
+
+            settings.put(StreamsConfig.DEFAULT_PRODUCTION_EXCEPTION_HANDLER_CLASS_CONFIG,
+                         IgnoreRecordTooLargeHandler.class);</pre></div>
+          </blockquote>
+        </div>
+        <div class="section" id="default-key-serde">
+          <h4><a class="toc-backref" href="#id8">default.key.serde</a><a class="headerlink" href="#default-key-serde" title="Permalink to this headline"></a></h4>
+          <blockquote>
+            <div><p>The default Serializer/Deserializer class for record keys. Serialization and deserialization in Kafka Streams happens
+              whenever data needs to be materialized, for example:</p>
+              <blockquote>
+                <div><ul class="simple">
+                  <li>Whenever data is read from or written to a <em>Kafka topic</em> (e.g., via the <code class="docutils literal"><span class="pre">StreamsBuilder#stream()</span></code> and <code class="docutils literal"><span class="pre">KStream#to()</span></code> methods).</li>
+                  <li>Whenever data is read from or written to a <em>state store</em>.</li>
+                </ul>
+                  <p>This is discussed in more detail in <a class="reference internal" href="datatypes.html#streams-developer-guide-serdes"><span class="std std-ref">Data types and serialization</span></a>.</p>
+                </div></blockquote>
+            </div></blockquote>
+        </div>
+        <div class="section" id="default-value-serde">
+          <h4><a class="toc-backref" href="#id9">default.value.serde</a><a class="headerlink" href="#default-value-serde" title="Permalink to this headline"></a></h4>
+          <blockquote>
+            <div><p>The default Serializer/Deserializer class for record values. Serialization and deserialization in Kafka Streams
+              happens whenever data needs to be materialized, for example:</p>
+              <ul class="simple">
+                <li>Whenever data is read from or written to a <em>Kafka topic</em> (e.g., via the <code class="docutils literal"><span class="pre">StreamsBuilder#stream()</span></code> and <code class="docutils literal"><span class="pre">KStream#to()</span></code> methods).</li>
+                <li>Whenever data is read from or written to a <em>state store</em>.</li>
+              </ul>
+              <p>This is discussed in more detail in <a class="reference internal" href="datatypes.html#streams-developer-guide-serdes"><span class="std std-ref">Data types and serialization</span></a>.</p>
+            </div></blockquote>
+        </div>
+        <div class="section" id="num-standby-replicas">
+          <span id="streams-developer-guide-standby-replicas"></span><h4><a class="toc-backref" href="#id10">num.standby.replicas</a><a class="headerlink" href="#num-standby-replicas" title="Permalink to this headline"></a></h4>
+          <blockquote>
+            <div>The number of standby replicas. Standby replicas are shadow copies of local state stores. Kafka Streams attempts to create the
+              specified number of replicas per store and keep them up to date as long as there are enough instances running.
+              Standby replicas are used to minimize the latency of task failover.  A task that was previously running on a failed instance is
+              preferred to restart on an instance that has standby replicas so that the local state store restoration process from its
+              changelog can be minimized.  Details about how Kafka Streams makes use of the standby replicas to minimize the cost of
+              resuming tasks on failover can be found in the <a class="reference internal" href="../architecture.html#streams_architecture_state"><span class="std std-ref">State</span></a> section.</div></blockquote>
+            </div>
+            <div class="admonition note">
+              <p class="first admonition-title">Note</p>
+              <p class="last">If you enable <cite>n</cite> standby tasks, you need to provision <cite>n+1</cite> <code class="docutils literal"><span class="pre">KafkaStreams</span></code>
+              instances.</p>
+              </div>
+        <div class="section" id="num-stream-threads">
+          <h4><a class="toc-backref" href="#id11">num.stream.threads</a><a class="headerlink" href="#num-stream-threads" title="Permalink to this headline"></a></h4>
+          <blockquote>
+            <div>This specifies the number of stream threads in an instance of the Kafka Streams application. The stream processing code runs in these thread.
+              For more information about Kafka Streams threading model, see <a class="reference internal" href="../architecture.html#streams_architecture_threads"><span class="std std-ref">Threading Model</span></a>.</div></blockquote>
+        </div>
+        <div class="section" id="partition-grouper">
+          <span id="streams-developer-guide-partition-grouper"></span><h4><a class="toc-backref" href="#id12">partition.grouper</a><a class="headerlink" href="#partition-grouper" title="Permalink to this headline"></a></h4>
+          <blockquote>
+            <div>A partition grouper creates a list of stream tasks from the partitions of source topics, where each created task is assigned with a group of source topic partitions.
+              The default implementation provided by Kafka Streams is <a class="reference external" href="../../../javadoc/org/apache/kafka/streams/processor/DefaultPartitionGrouper.html">DefaultPartitionGrouper</a>.
+              It assigns each task with one partition for each of the source topic partitions. The generated number of tasks equals the largest
+              number of partitions among the input topics. Usually an application does not need to customize the partition grouper.</div></blockquote>
+        </div>
+        <div class="section" id="processing-guarantee">
+          <span id="streams-developer-guide-processing-guarantee"></span><h4><a class="toc-backref" href="#id25">processing.guarantee</a><a class="headerlink" href="#processing-guarantee" title="Permalink to this headline"></a></h4>
+          <blockquote>
+            <div>The processing guarantee that should be used. Possible values are <code class="docutils literal"><span class="pre">"at_least_once"</span></code> (default) and <code class="docutils literal"><span class="pre">"exactly_once"</span></code>.
+                 Note that if exactly-once processing is enabled, the default for parameter <code class="docutils literal"><span class="pre">commit.interval.ms</span></code> changes to 100ms.
+                 Additionally, consumers are configured with <code class="docutils literal"><span class="pre">isolation.level="read_committed"</span></code>
+                 and producers are configured with <code class="docutils literal"><span class="pre">retries=Integer.MAX_VALUE</span></code>, <code class="docutils literal"><span class="pre">enable.idempotence=true</span></code>,
+                 and <code class="docutils literal"><span class="pre">max.in.flight.requests.per.connection=1</span></code> per default.
+                 Note that by default exactly-once processing requires a cluster of at least three brokers what is the recommended setting for production.
+                 For development you can change this, by adjusting broker setting <code class="docutils literal"><span class="pre">transaction.state.log.replication.factor</span></code> and <code class="docutils literal"><span class="pre">transaction.state.log.min.isr</span></code> to the number of broker you want to use.
+                 For more details see <a href="../core-concepts#streams_processing_guarantee">Processing Guarantees</a>.
+            </div></blockquote>
+        </div>
+        <div class="section" id="replication-factor">
+          <span id="replication-factor-parm"></span><h4><a class="toc-backref" href="#id13">replication.factor</a><a class="headerlink" href="#replication-factor" title="Permalink to this headline"></a></h4>
+          <blockquote>
+            <div><p>This specifies the replication factor of internal topics that Kafka Streams creates when local states are used or a stream is
+              repartitioned for aggregation. Replication is important for fault tolerance. Without replication even a single broker failure
+              may prevent progress of the stream processing application. It is recommended to use a similar replication factor as source topics.</p>
+              <dl class="docutils">
+                <dt>Recommendation:</dt>
+                <dd>Increase the replication factor to 3 to ensure that the internal Kafka Streams topic can tolerate up to 2 broker failures.
+                  Note that you will require more storage space as well (3 times more with the replication factor of 3).</dd>
+              </dl>
+            </div></blockquote>
+        </div>
+        <div class="section" id="state-dir">
+          <h4><a class="toc-backref" href="#id14">state.dir</a><a class="headerlink" href="#state-dir" title="Permalink to this headline"></a></h4>
+          <blockquote>
+            <div>The state directory. Kafka Streams persists local states under the state directory. Each application has a subdirectory on its hosting
+              machine that is located under the state directory. The name of the subdirectory is the application ID. The state stores associated
+              with the application are created under this subdirectory.</div></blockquote>
+        </div>
+        <div class="section" id="timestamp-extractor">
+          <span id="streams-developer-guide-timestamp-extractor"></span><h4><a class="toc-backref" href="#id15">timestamp.extractor</a><a class="headerlink" href="#timestamp-extractor" title="Permalink to this headline"></a></h4>
+          <blockquote>
+            <div><p>A timestamp extractor pulls a timestamp from an instance of <a class="reference external" href="../../../javadoc/org/apache/kafka/clients/consumer/ConsumerRecord.html">ConsumerRecord</a>.
+              Timestamps are used to control the progress of streams.</p>
+              <p>The default extractor is
+                <a class="reference external" href="../../../javadoc/org/apache/kafka/streams/processor/FailOnInvalidTimestamp.html">FailOnInvalidTimestamp</a>.
+                This extractor retrieves built-in timestamps that are automatically embedded into Kafka messages by the Kafka producer
+                client since
+                <a class="reference external" href="https://cwiki.apache.org/confluence/display/KAFKA/KIP-32+-+Add+timestamps+to+Kafka+message">Kafka version 0.10</a>.
+                Depending on the setting of Kafka&#8217;s server-side <code class="docutils literal"><span class="pre">log.message.timestamp.type</span></code> broker and <code class="docutils literal"><span class="pre">message.timestamp.type</span></code> topic parameters,
+                this extractor provides you with:</p>
+              <ul class="simple">
+                <li><strong>event-time</strong> processing semantics if <code class="docutils literal"><span class="pre">log.message.timestamp.type</span></code> is set to <code class="docutils literal"><span class="pre">CreateTime</span></code> aka &#8220;producer time&#8221;
+                  (which is the default).  This represents the time when a Kafka producer sent the original message.  If you use Kafka&#8217;s
+                  official producer client, the timestamp represents milliseconds since the epoch.</li>
+                <li><strong>ingestion-time</strong> processing semantics if <code class="docutils literal"><span class="pre">log.message.timestamp.type</span></code> is set to <code class="docutils literal"><span class="pre">LogAppendTime</span></code> aka &#8220;broker
+                  time&#8221;.  This represents the time when the Kafka broker received the original message, in milliseconds since the epoch.</li>
+              </ul>
+              <p>The <code class="docutils literal"><span class="pre">FailOnInvalidTimestamp</span></code> extractor throws an exception if a record contains an invalid (i.e. negative) built-in
+                timestamp, because Kafka Streams would not process this record but silently drop it.  Invalid built-in timestamps can
+                occur for various reasons:  if for example, you consume a topic that is written to by pre-0.10 Kafka producer clients
+                or by third-party producer clients that don&#8217;t support the new Kafka 0.10 message format yet;  another situation where
+                this may happen is after upgrading your Kafka cluster from <code class="docutils literal"><span class="pre">0.9</span></code> to <code class="docutils literal"><span class="pre">0.10</span></code>, where all the data that was generated
+                with <code class="docutils literal"><span class="pre">0.9</span></code> does not include the <code class="docutils literal"><span class="pre">0.10</span></code> message timestamps.</p>
+              <p>If you have data with invalid timestamps and want to process it, then there are two alternative extractors available.
+                Both work on built-in timestamps, but handle invalid timestamps differently.</p>
+              <ul class="simple">
+                <li><a class="reference external" href="../../../javadoc/org/apache/kafka/streams/processor/LogAndSkipOnInvalidTimestamp.html">LogAndSkipOnInvalidTimestamp</a>:
+                  This extractor logs a warn message and returns the invalid timestamp to Kafka Streams, which will not process but
+                  silently drop the record.
+                  This log-and-skip strategy allows Kafka Streams to make progress instead of failing if there are records with an
+                  invalid built-in timestamp in your input data.</li>
+                <li><a class="reference external" href="../../../javadoc/org/apache/kafka/streams/processor/UsePreviousTimeOnInvalidTimestamp.html">UsePreviousTimeOnInvalidTimestamp</a>.
+                  This extractor returns the record&#8217;s built-in timestamp if it is valid (i.e. not negative).  If the record does not
+                  have a valid built-in timestamps, the extractor returns the previously extracted valid timestamp from a record of the
+                  same topic partition as the current record as a timestamp estimation.  In case that no timestamp can be estimated, it
+                  throws an exception.</li>
+              </ul>
+              <p>Another built-in extractor is
+                <a class="reference external" href="../../../javadoc/org/apache/kafka/streams/processor/WallclockTimestampExtractor.html">WallclockTimestampExtractor</a>.
+                This extractor does not actually &#8220;extract&#8221; a timestamp from the consumed record but rather returns the current time in
+                milliseconds from the system clock (think: <code class="docutils literal"><span class="pre">System.currentTimeMillis()</span></code>), which effectively means Streams will operate
+                on the basis of the so-called <strong>processing-time</strong> of events.</p>
+              <p>You can also provide your own timestamp extractors, for instance to retrieve timestamps embedded in the payload of
+                messages.  If you cannot extract a valid timestamp, you can either throw an exception, return a negative timestamp, or
+                estimate a timestamp.  Returning a negative timestamp will result in data loss &#8211; the corresponding record will not be
+                processed but silently dropped.  If you want to estimate a new timestamp, you can use the value provided via
+                <code class="docutils literal"><span class="pre">previousTimestamp</span></code> (i.e., a Kafka Streams timestamp estimation).  Here is an example of a custom
+                <code class="docutils literal"><span class="pre">TimestampExtractor</span></code> implementation:</p>
+              <div class="highlight-java"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">org.apache.kafka.clients.consumer.ConsumerRecord</span><span class="o">;</span>
+<span class="kn">import</span> <span class="nn">org.apache.kafka.streams.processor.TimestampExtractor</span><span class="o">;</span>
+
+<span class="c1">// Extracts the embedded timestamp of a record (giving you &quot;event-time&quot; semantics).</span>
+<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyEventTimeExtractor</span> <span class="kd">implements</span> <span class="n">TimestampExtractor</span> <span class="o">{</span>
+
+  <span class="nd">@Override</span>
+  <span class="kd">public</span> <span class="kt">long</span> <span class="nf">extract</span><span class="o">(</span><span class="kd">final</span> <span class="n">ConsumerRecord</span><span class="o">&lt;</span><span class="n">Object</span><span class="o">,</span> <span class="n">Object</span><span class="o">&gt;</span> <span class="n">record</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">long</span> <span class="n">previousTimestamp</span><span class="o">) [...]
+    <span class="c1">// `Foo` is your own custom class, which we assume has a method that returns</span>
+    <span class="c1">// the embedded timestamp (milliseconds since midnight, January 1, 1970 UTC).</span>
+    <span class="kt">long</span> <span class="n">timestamp</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="o">;</span>
+    <span class="kd">final</span> <span class="n">Foo</span> <span class="n">myPojo</span> <span class="o">=</span> <span class="o">(</span><span class="n">Foo</span><span class="o">)</span> <span class="n">record</span><span class="o">.</span><span class="na">value</span><span class="o">();</span>
+    <span class="k">if</span> <span class="o">(</span><span class="n">myPojo</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
+      <span class="n">timestamp</span> <span class="o">=</span> <span class="n">myPojo</span><span class="o">.</span><span class="na">getTimestampInMillis</span><span class="o">();</span>
+    <span class="o">}</span>
+    <span class="k">if</span> <span class="o">(</span><span class="n">timestamp</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
+      <span class="c1">// Invalid timestamp!  Attempt to estimate a new timestamp,</span>
+      <span class="c1">// otherwise fall back to wall-clock time (processing-time).</span>
+      <span class="k">if</span> <span class="o">(</span><span class="n">previousTimestamp</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
+        <span class="k">return</span> <span class="n">previousTimestamp</span><span class="o">;</span>
+      <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
+        <span class="k">return</span> <span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
+      <span class="o">}</span>
+    <span class="o">}</span>
+  <span class="o">}</span>
+
+<span class="o">}</span>
+</pre></div>
+              </div>
+              <p>You would then define the custom timestamp extractor in your Streams configuration as follows:</p>
+              <div class="highlight-java"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">java.util.Properties</span><span class="o">;</span>
+<span class="kn">import</span> <span class="nn">org.apache.kafka.streams.StreamsConfig</span><span class="o">;</span>
+
+<span class="n">Properties</span> <span class="n">streamsConfiguration</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Properties</span><span class="o">();</span>
+<span class="n">streamsConfiguration</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">StreamsConfig</span><span class="o">.</span><span class="na">TIMESTAMP_EXTRACTOR_CLASS_CONFIG</span><span class="o">,</span> <span class="n">MyEventTimeExtractor</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+</pre></div>
+              </div>
+            </div></blockquote>
+        </div>
+      </div>
+      <div class="section" id="kafka-consumers-and-producer-configuration-parameters">
+        <h3><a class="toc-backref" href="#id16">Kafka consumers, producer and admin client configuration parameters</a><a class="headerlink" href="#kafka-consumers-and-producer-configuration-parameters" title="Permalink to this headline"></a></h3>
+        <p>You can specify parameters for the Kafka <a class="reference external" href="../../../javadoc/org/apache/kafka/clients/consumer/package-summary.html">consumers</a>, <a class="reference external" href="../../../javadoc/org/apache/kafka/clients/producer/package-summary.html">producers</a>,
+            and <a class="reference external" href="../../../javadoc/org/apache/kafka/kafka/clients/admin/package-summary.html">admin client</a> that are used internally.
+            The consumer, producer and admin client settings are defined by specifying parameters in a <code class="docutils literal"><span class="pre">StreamsConfig</span></code> instance.</p>
+        <p>In this example, the Kafka <a class="reference external" href="../../../javadoc/org/apache/kafka/clients/consumer/ConsumerConfig.html#SESSION_TIMEOUT_MS_CONFIG">consumer session timeout</a> is configured to be 60000 milliseconds in the Streams settings:</p>
+        <div class="highlight-java"><div class="highlight"><pre><span></span><span class="n">Properties</span> <span class="n">streamsSettings</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Properties</span><span class="o">();</span>
+<span class="c1">// Example of a &quot;normal&quot; setting for Kafka Streams</span>
+<span class="n">streamsSettings</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">StreamsConfig</span><span class="o">.</span><span class="na">BOOTSTRAP_SERVERS_CONFIG</span><span class="o">,</span> <span class="s">&quot;kafka-broker-01:9092&quot;</span><span class="o">);</span>
+<span class="c1">// Customize the Kafka consumer settings of your Streams application</span>
+<span class="n">streamsSettings</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">ConsumerConfig</span><span class="o">.</span><span class="na">SESSION_TIMEOUT_MS_CONFIG</span><span class="o">,</span> <span class="mi">60000</span><span class="o">);</span>
+</pre></div>
+        </div>
+        <div class="section" id="naming">
+          <h4><a class="toc-backref" href="#id17">Naming</a><a class="headerlink" href="#naming" title="Permalink to this headline"></a></h4>
+          <p>Some consumer, producer and admin client configuration parameters use the same parameter name, and Kafka Streams library itself also uses some parameters that share the same name with its embedded client. For example, <code class="docutils literal"><span class="pre">send.buffer.bytes</span></code> and
+              <code class="docutils literal"><span class="pre">receive.buffer.bytes</span></code> are used to configure TCP buffers; <code class="docutils literal"><span class="pre">request.timeout.ms</span></code> and <code class="docutils literal"><span class="pre">retry.backoff.ms</span></code> control retries for client request;
+              <code class="docutils literal"><span class="pre">retries</span></code> are used to configure how many retries are allowed when handling retriable errors from broker request responses.
+              You can avoid duplicate names by prefix parameter names with <code class="docutils literal"><span class="pre">consumer.</span></code>, <code class="docutils literal"><span class="pre">producer.</span></code>, or <code class="docutils literal"><span class="pre">admin.</span></code> (e.g., <code class="docutils literal"><span class="pre">consumer.send.buffer.bytes</span></code> and <code class="docutils literal"><span class="pre">producer.send.buffer.bytes</span></code>).</p>
+          <div class="highlight-java"><div class="highlight"><pre><span></span><span class="n">Properties</span> <span class="n">streamsSettings</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Properties</span><span class="o">();</span>
+<span class="c1">// same value for consumer, producer, and admin client</span>
+<span class="n">streamsSettings</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">&quot;PARAMETER_NAME&quot;</span><span class="o">,</span> <span class="s">&quot;value&quot;</span><span class="o">);</span>
+<span class="c1">// different values for consumer and producer</span>
+<span class="n">streamsSettings</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">&quot;consumer.PARAMETER_NAME&quot;</span><span class="o">,</span> <span class="s">&quot;consumer-value&quot;</span><span class="o">);</span>
+<span class="n">streamsSettings</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">&quot;producer.PARAMETER_NAME&quot;</span><span class="o">,</span> <span class="s">&quot;producer-value&quot;</span><span class="o">);</span>
+<span class="n">streamsSettings</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">&quot;admin.PARAMETER_NAME&quot;</span><span class="o">,</span> <span class="s">&quot;admin-value&quot;</span><span class="o">);</span>
+<span class="c1">// alternatively, you can use</span>
+<span class="n">streamsSettings</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">StreamsConfig</span><span class="o">.</span><span class="na">consumerPrefix</span><span class="o">(</span><span class="s">&quot;PARAMETER_NAME&quot;</span><span class="o">),</span> <span class="s">&quot;consumer-value&quot;</span><span class="o">);</span>
+<span class="n">streamsSettings</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">StreamsConfig</span><span class="o">.</span><span class="na">producerPrefix</span><span class="o">(</span><span class="s">&quot;PARAMETER_NAME&quot;</span><span class="o">),</span> <span class="s">&quot;producer-value&quot;</span><span class="o">);</span>
+<span class="n">streamsSettings</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">StreamsConfig</span><span class="o">.</span><span class="na">adminClientPrefix</span><span class="o">(</span><span class="s">&quot;PARAMETER_NAME&quot;</span><span class="o">),</span> <span class="s">&quot;admin-value&quot;</span><span class="o">);</span>
+</pre></div>
+          <p>You could further separate consumer configuration by adding different prefixes:</p>
+          <ul class="simple">
+            <li><code class="docutils literal"><span class="pre">main.consumer.</span></code> for main consumer which is the default consumer of stream source.</li>
+            <li><code class="docutils literal"><span class="pre">restore.consumer.</span></code> for restore consumer which is in charge of state store recovery.</li>
+            <li><code class="docutils literal"><span class="pre">global.consumer.</span></code> for global consumer which is used in global KTable construction.</li>
+          </ul>
+          <p>For example, if you only want to set restore consumer config without touching other consumers' settings, you could simply use <code class="docutils literal"><span class="pre">restore.consumer.</span></code> to set the config.</p>
+          <div class="highlight-java"><div class="highlight"><pre><span></span><span class="n">Properties</span> <span class="n">streamsSettings</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Properties</span><span class="o">();</span>
+<span class="c1">// same config value for all consumer types</span>
+<span class="n">streamsSettings</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">&quot;consumer.PARAMETER_NAME&quot;</span><span class="o">,</span> <span class="s">&quot;general-consumer-value&quot;</span><span class="o">);</span>
+<span class="c1">// set a different restore consumer config. This would make restore consumer take restore-consumer-value,</span>
+<span>// while main consumer and global consumer stay with general-consumer-value</span>
+<span class="n">streamsSettings</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">&quot;restore.consumer.PARAMETER_NAME&quot;</span><span class="o">,</span> <span class="s">&quot;restore-consumer-value&quot;</span><span class="o">);</span>
+<span class="c1">// alternatively, you can use</span>
+<span class="n">streamsSettings</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">StreamsConfig</span><span class="o">.</span><span class="na">restoreConsumerPrefix</span><span class="o">(</span><span class="s">&quot;PARAMETER_NAME&quot;</span><span class="o">),</span> <span class="s">&quot;restore-consumer-value&quot;</span><span class="o">);</span>
+</pre></div>
+          </div>
+          <p> Same applied to <code class="docutils literal"><span class="pre">main.consumer.</span></code> and <code class="docutils literal"><span class="pre">main.consumer.</span></code>, if you only want to specify one consumer type config.</p>
+          </div>
+        </div>
+        <div class="section" id="default-values">
+          <h4><a class="toc-backref" href="#id18">Default Values</a><a class="headerlink" href="#default-values" title="Permalink to this headline"></a></h4>
+          <p>Kafka Streams uses different default values for some of the underlying client configs, which are summarized below. For detailed descriptions
+            of these configs, see <a class="reference external" href="http://kafka.apache.org/0100/documentation.html#producerconfigs">Producer Configs</a>
+            and <a class="reference external" href="http://kafka.apache.org/0100/documentation.html#newconsumerconfigs">Consumer Configs</a>.</p>
+          <table border="1" class="non-scrolling-table docutils">
+            <thead valign="bottom">
+            <tr class="row-odd"><th class="head">Parameter Name</th>
+              <th class="head">Corresponding Client</th>
+              <th class="head">Streams Default</th>
+            </tr>
+            </thead>
+            <tbody valign="top">
+            <tr class="row-even"><td>auto.offset.reset</td>
+                <td>Global Consumer</td>
+                <td>none (cannot be changed)</td>
+            </tr>
+            <tr class="row-even"><td>auto.offset.reset</td>
+                <td>Restore Consumer</td>
+                <td>none (cannot be changed)</td>
+            </tr>
+            <tr class="row-even"><td>auto.offset.reset</td>
+              <td>Consumer</td>
+              <td>earliest</td>
+            </tr>
+            <tr class="row-odd"><td>enable.auto.commit</td>
+              <td>Consumer</td>
+              <td>false</td>
+            </tr>
+            <tr class="row-even"><td>linger.ms</td>
+              <td>Producer</td>
+              <td>100</td>
+            </tr>
+            <tr class="row-odd"><td>max.poll.interval.ms</td>
+              <td>Consumer</td>
+              <td>Integer.MAX_VALUE</td>
+            </tr>
+            <tr class="row-even"><td>max.poll.records</td>
+              <td>Consumer</td>
+              <td>1000</td>
+            </tr>
+            <tr class="row-odd"><td>retries</td>
+              <td>Producer</td>
+              <td>10</td>
+            </tr>
+            <tr class="row-even"><td>rocksdb.config.setter</td>
+              <td>Consumer</td>
+              <td>&nbsp;</td>
+            </tr>
+            </tbody>
+          </table>
+        </div>
+        <div class="section" id="enable-auto-commit">
+          <span id="streams-developer-guide-consumer-auto-commit"></span><h4><a class="toc-backref" href="#id19">enable.auto.commit</a><a class="headerlink" href="#enable-auto-commit" title="Permalink to this headline"></a></h4>
+          <blockquote>
+            <div>The consumer auto commit. To guarantee at-least-once processing semantics and turn off auto commits, Kafka Streams overrides this consumer config
+              value to <code class="docutils literal"><span class="pre">false</span></code>.  Consumers will only commit explicitly via <em>commitSync</em> calls when the Kafka Streams library or a user decides
+              to commit the current processing state.</div></blockquote>
+        </div>
+        <div class="section" id="rocksdb-config-setter">
+          <span id="streams-developer-guide-rocksdb-config"></span><h4><a class="toc-backref" href="#id20">rocksdb.config.setter</a><a class="headerlink" href="#rocksdb-config-setter" title="Permalink to this headline"></a></h4>
+          <blockquote>
+            <div><p>The RocksDB configuration. Kafka Streams uses RocksDB as the default storage engine for persistent stores. To change the default
+              configuration for RocksDB, implement <code class="docutils literal"><span class="pre">RocksDBConfigSetter</span></code> and provide your custom class via <a class="reference external" href="../../../javadoc/org/apache/kafka/streams/state/RocksDBConfigSetter.html">rocksdb.config.setter</a>.</p>
+              <p>Here is an example that adjusts the memory size consumed by RocksDB.</p>
+              <div class="highlight-java"><div class="highlight"><pre><span></span>    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">CustomRocksDBConfig</span> <span class="kd">implements</span> <span class="n">RocksDBConfigSetter</span> <span class="o">{</span>
+
+       <span class="nd">@Override</span>
+       <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setConfig</span><span class="o">(</span><span class="kd">final</span> <span class="n">String</span> <span class="n">storeName</span><span class="o">,</span> <span class="kd">final</span> <span class="n">Options</span> <span class="n">options</span><span class="o">,</span> <span class="kd">final</span> <span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span [...]
+         <span class="c1">// See #1 below.</span>
+         <span class="n">BlockBasedTableConfig</span> <span class="n">tableConfig</span> <span class="o">=</span> <span class="k">new</span> <span class="n">org</span><span class="o">.</span><span class="na">rocksdb</span><span class="o">.</span><span class="na">BlockBasedTableConfig</span><span class="o">();</span>
+         <span class="n">tableConfig</span><span class="o">.</span><span class="na">setBlockCacheSize</span><span class="o">(</span><span class="mi">16</span> <span class="o">*</span> <span class="mi">1024</span> <span class="o">*</span> <span class="mi">1024L</span><span class="o">);</span>
+         <span class="c1">// See #2 below.</span>
+         <span class="n">tableConfig</span><span class="o">.</span><span class="na">setBlockSize</span><span class="o">(</span><span class="mi">16</span> <span class="o">*</span> <span class="mi">1024L</span><span class="o">);</span>
+         <span class="c1">// See #3 below.</span>
+         <span class="n">tableConfig</span><span class="o">.</span><span class="na">setCacheIndexAndFilterBlocks</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
+         <span class="n">options</span><span class="o">.</span><span class="na">setTableFormatConfig</span><span class="o">(</span><span class="n">tableConfig</span><span class="o">);</span>
+         <span class="c1">// See #4 below.</span>
+         <span class="n">options</span><span class="o">.</span><span class="na">setMaxWriteBufferNumber</span><span class="o">(</span><span class="mi">2</span><span class="o">);</span>
+       <span class="o">}</span>
+    <span class="o">}</span>
+
+<span class="n">Properties</span> <span class="n">streamsSettings</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Properties</span><span class="o">();</span>
+<span class="n">streamsConfig</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">StreamsConfig</span><span class="o">.</span><span class="na">ROCKSDB_CONFIG_SETTER_CLASS_CONFIG</span><span class="o">,</span> <span class="n">CustomRocksDBConfig</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+</pre></div>
+              </div>
+              <dl class="docutils">
+                <dt>Notes for example:</dt>
+                <dd><ol class="first last arabic simple">
+                  <li><code class="docutils literal"><span class="pre">BlockBasedTableConfig</span> <span class="pre">tableConfig</span> <span class="pre">=</span> <span class="pre">new</span> <span class="pre">org.rocksdb.BlockBasedTableConfig();</span></code> Reduce block cache size from the default, shown <a class="reference external" href="https://github.com/apache/kafka/blob/1.0/streams/src/main/java/org/apache/kafka/streams/state/internals/RocksDBStore.java#L81">here</a>,  as the t [...]
+                  <li><code class="docutils literal"><span class="pre">tableConfig.setBlockSize(16</span> <span class="pre">*</span> <span class="pre">1024L);</span></code> Modify the default <a class="reference external" href="https://github.com/apache/kafka/blob/1.0/streams/src/main/java/org/apache/kafka/streams/state/internals/RocksDBStore.java#L82">block size</a> per these instructions from the <a class="reference external" href="https://github.com/facebook/rocksdb/wiki/Memory-usage- [...]
+                  <li><code class="docutils literal"><span class="pre">tableConfig.setCacheIndexAndFilterBlocks(true);</span></code> Do not let the index and filter blocks grow unbounded. For more information, see the <a class="reference external" href="https://github.com/facebook/rocksdb/wiki/Block-Cache#caching-index-and-filter-blocks">RocksDB GitHub</a>.</li>
+                  <li><code class="docutils literal"><span class="pre">options.setMaxWriteBufferNumber(2);</span></code> See the advanced options in the <a class="reference external" href="https://github.com/facebook/rocksdb/blob/8dee8cad9ee6b70fd6e1a5989a8156650a70c04f/include/rocksdb/advanced_options.h#L103">RocksDB GitHub</a>.</li>
+                </ol>
+                </dd>
+              </dl>
+            </div></blockquote>
+        </div>
+      </div>
+      <div class="section" id="recommended-configuration-parameters-for-resiliency">
+        <h3><a class="toc-backref" href="#id21">Recommended configuration parameters for resiliency</a><a class="headerlink" href="#recommended-configuration-parameters-for-resiliency" title="Permalink to this headline"></a></h3>
+        <p>There are several Kafka and Kafka Streams configuration options that need to be configured explicitly for resiliency in face of broker failures:</p>
+        <table border="1" class="non-scrolling-table docutils">
+          <thead valign="bottom">
+          <tr class="row-odd"><th class="head">Parameter Name</th>
+            <th class="head">Corresponding Client</th>
+            <th class="head">Default value</th>
+            <th class="head">Consider setting to</th>
+          </tr>
+          </thead>
+          <tbody valign="top">
+          <tr class="row-even"><td>acks</td>
+            <td>Producer</td>
+            <td><code class="docutils literal"><span class="pre">acks=1</span></code></td>
+            <td><code class="docutils literal"><span class="pre">acks=all</span></code></td>
+          </tr>
+          <tr class="row-odd"><td>replication.factor</td>
+            <td>Streams</td>
+            <td><code class="docutils literal"><span class="pre">1</span></code></td>
+            <td><code class="docutils literal"><span class="pre">3</span></code></td>
+          </tr>
+          <tr class="row-even"><td>min.insync.replicas</td>
+            <td>Broker</td>
+            <td><code class="docutils literal"><span class="pre">1</span></code></td>
+            <td><code class="docutils literal"><span class="pre">2</span></code></td>
+          </tr>
+          </tbody>
+        </table>
+        <p>Increasing the replication factor to 3 ensures that the internal Kafka Streams topic can tolerate up to 2 broker failures. Changing the acks setting to &#8220;all&#8221;
+          guarantees that a record will not be lost as long as one replica is alive. The tradeoff from moving to the default values to the recommended ones is
+          that some performance and more storage space (3x with the replication factor of 3) are sacrificed for more resiliency.</p>
+        <div class="section" id="acks">
+          <h4><a class="toc-backref" href="#id22">acks</a><a class="headerlink" href="#acks" title="Permalink to this headline"></a></h4>
+          <blockquote>
+            <div><p>The number of acknowledgments that the leader must have received before considering a request complete. This controls
+              the durability of records that are sent. The possible values are:</p>
+              <ul class="simple">
+                <li><code class="docutils literal"><span class="pre">acks=0</span></code> The producer does not wait for acknowledgment from the server and the record is immediately added to the socket buffer and considered sent. No guarantee can be made that the server has received the record in this case, and the <code class="docutils literal"><span class="pre">retries</span></code> configuration will not take effect (as the client won&#8217;t generally know of any failures). The offse [...]
+                <li><code class="docutils literal"><span class="pre">acks=1</span></code> The leader writes the record to its local log and responds without waiting for full acknowledgement from all followers. If the leader immediately fails after acknowledging the record, but before the followers have replicated it, then the record will be lost.</li>
+                <li><code class="docutils literal"><span class="pre">acks=all</span></code> The leader waits for the full set of in-sync replicas to acknowledge the record. This guarantees that the record will not be lost if there is at least one in-sync replica alive. This is the strongest available guarantee.</li>
+              </ul>
+              <p>For more information, see the <a class="reference external" href="https://kafka.apache.org/documentation/#producerconfigs">Kafka Producer documentation</a>.</p>
+            </div></blockquote>
+        </div>
+        <div class="section" id="id2">
+          <h4><a class="toc-backref" href="#id23">replication.factor</a><a class="headerlink" href="#id2" title="Permalink to this headline"></a></h4>
+          <blockquote>
+            <div>See the <a class="reference internal" href="#replication-factor-parm"><span class="std std-ref">description here</span></a>.</div></blockquote>
+          <div class="highlight-java"><div class="highlight"><pre><span></span><span class="n">Properties</span> <span class="n">streamsSettings</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Properties</span><span class="o">();</span>
+<span class="n">streamsSettings</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">StreamsConfig</span><span class="o">.</span><span class="na">REPLICATION_FACTOR_CONFIG</span><span class="o">,</span> <span class="mi">3</span><span class="o">);</span>
+<span class="n">streamsSettings</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">StreamsConfig</span><span class="o">.</span><span class="na">producerPrefix</span><span class="o">(</span><span class="n">ProducerConfig</span><span class="o">.</span><span class="na">ACKS_CONFIG</span><span class="o">),</span> <span class="s">&quot;all&quot;</span><span class="o">);</span>
+</pre></div>
+          </div>
+</div>
+</div>
+</div>
+</div>
+
+
+               </div>
+              </div>
+              <div class="pagination">
+                <a href="/{{version}}/documentation/streams/developer-guide/write-streams" class="pagination__btn pagination__btn__prev">Previous</a>
+                <a href="/{{version}}/documentation/streams/developer-guide/dsl-api" class="pagination__btn pagination__btn__next">Next</a>
+              </div>
+                </script>
+
+                <!--#include virtual="../../../includes/_header.htm" -->
+                <!--#include virtual="../../../includes/_top.htm" -->
+                    <div class="content documentation documentation--current">
+                    <!--#include virtual="../../../includes/_nav.htm" -->
+                    <div class="right">
+                    <!--#include virtual="../../../includes/_docs_banner.htm" -->
+                    <ul class="breadcrumbs">
+                    <li><a href="/documentation">Documentation</a></li>
+                    <li><a href="/documentation/streams">Kafka Streams</a></li>
+                    <li><a href="/documentation/streams/developer-guide/">Developer Guide</a></li>
+                </ul>
+                <div class="p-content"></div>
+                    </div>
+                    </div>
+                    <!--#include virtual="../../../includes/_footer.htm" -->
+                    <script>
+                    $(function() {
+                        // Show selected style on nav item
+                        $('.b-nav__streams').addClass('selected');
+
+                        //sticky secondary nav
+                        var $navbar = $(".sub-nav-sticky"),
+                            y_pos = $navbar.offset().top,
+                            height = $navbar.height();
+
+                        $(window).scroll(function() {
+                            var scrollTop = $(window).scrollTop();
+
+                            if (scrollTop > y_pos - height) {
+                                $navbar.addClass("navbar-fixed")
+                            } else if (scrollTop <= y_pos) {
+                                $navbar.removeClass("navbar-fixed")
+                            }
+                        });
+
+                        // Display docs subnav items
+                        $('.b-nav__docs').parent().toggleClass('nav__item__with__subs--expanded');
+                    });
+              </script>
diff --git a/21/streams/developer-guide/datatypes.html b/21/streams/developer-guide/datatypes.html
new file mode 100644
index 0000000..6c7869c
--- /dev/null
+++ b/21/streams/developer-guide/datatypes.html
@@ -0,0 +1,239 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<script><!--#include virtual="../../js/templateData.js" --></script>
+
+<script id="content-template" type="text/x-handlebars-template">
+  <!-- h1>Developer Guide for Kafka Streams</h1 -->
+  <div class="sub-nav-sticky">
+    <div class="sticky-top">
+      <!-- div style="height:35px">
+        <a href="/{{version}}/documentation/streams/">Introduction</a>
+        <a class="active-menu-item" href="/{{version}}/documentation/streams/developer-guide">Developer Guide</a>
+        <a href="/{{version}}/documentation/streams/core-concepts">Concepts</a>
+        <a href="/{{version}}/documentation/streams/quickstart">Run Demo App</a>
+        <a href="/{{version}}/documentation/streams/tutorial">Tutorial: Write App</a>
+      </div -->
+    </div>
+  </div>
+
+  <div class="section" id="data-types-and-serialization">
+    <span id="streams-developer-guide-serdes"></span><h1>Data Types and Serialization<a class="headerlink" href="#data-types-and-serialization" title="Permalink to this headline"></a></h1>
+    <p>Every Kafka Streams application must provide SerDes (Serializer/Deserializer) for the data types of record keys and record values (e.g. <code class="docutils literal"><span class="pre">java.lang.String</span></code>) to materialize the data when necessary.  Operations that require such SerDes information include: <code class="docutils literal"><span class="pre">stream()</span></code>, <code class="docutils literal"><span class="pre">table()</span></code>, <code class="docutils lit [...]
+    <p>You can provide SerDes by using either of these methods:</p>
+    <ul class="simple">
+      <li>By setting default SerDes in the <code class="docutils literal"><span class="pre">java.util.Properties</span></code> config instance.</li>
+      <li>By specifying explicit SerDes when calling the appropriate API methods, thus overriding the defaults.</li>
+    </ul>
+
+      <p class="topic-title first"><b>Table of Contents</b></p>
+      <ul class="simple">
+          <li><a class="reference internal" href="#configuring-serdes" id="id1">Configuring SerDes</a></li>
+          <li><a class="reference internal" href="#overriding-default-serdes" id="id2">Overriding default SerDes</a></li>
+          <li><a class="reference internal" href="#available-serdes" id="id3">Available SerDes</a></li>
+          <ul>
+              <li><a class="reference internal" href="#primitive-and-basic-types" id="id4">Primitive and basic types</a></li>
+              <li><a class="reference internal" href="#avro" id="id5">Avro</a></li>
+              <li><a class="reference internal" href="#json" id="id6">JSON</a></li>
+              <li><a class="reference internal" href="#further-serdes" id="id7">Further serdes</a></li>
+          </ul>
+          <li><a class="reference internal" href="#scala-dsl-serdes" id="id8">Kafka Streams DSL for Scala Implicit SerDes</a></li>
+      </ul>
+    <div class="section" id="configuring-serdes">
+      <h2>Configuring SerDes<a class="headerlink" href="#configuring-serdes" title="Permalink to this headline"></a></h2>
+      <p>SerDes specified in the Streams configuration are used as the default in your Kafka Streams application.</p>
+      <div class="highlight-java"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">org.apache.kafka.common.serialization.Serdes</span><span class="o">;</span>
+<span class="kn">import</span> <span class="nn">org.apache.kafka.streams.StreamsConfig</span><span class="o">;</span>
+
+<span class="n">Properties</span> <span class="n">settings</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Properties</span><span class="o">();</span>
+<span class="c1">// Default serde for keys of data records (here: built-in serde for String type)</span>
+<span class="n">settings</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">StreamsConfig</span><span class="o">.</span><span class="na">KEY_SERDE_CLASS_CONFIG</span><span class="o">,</span> <span class="n">Serdes</span><span class="o">.</span><span class="na">String</span><span class="o">().</span><span class="na">getClass</span><span class="o">().</span><span class="na">getName</span><span class="o">());</span>
+<span class="c1">// Default serde for values of data records (here: built-in serde for Long type)</span>
+<span class="n">settings</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">StreamsConfig</span><span class="o">.</span><span class="na">VALUE_SERDE_CLASS_CONFIG</span><span class="o">,</span> <span class="n">Serdes</span><span class="o">.</span><span class="na">Long</span><span class="o">().</span><span class="na">getClass</span><span class="o">().</span><span class="na">getName</span><span class="o">());</span>
+</pre></div>
+      </div>
+    </div>
+    <div class="section" id="overriding-default-serdes">
+      <h2>Overriding default SerDes<a class="headerlink" href="#overriding-default-serdes" title="Permalink to this headline"></a></h2>
+      <p>You can also specify SerDes explicitly by passing them to the appropriate API methods, which overrides the default serde settings:</p>
+      <div class="highlight-java"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">org.apache.kafka.common.serialization.Serde</span><span class="o">;</span>
+<span class="kn">import</span> <span class="nn">org.apache.kafka.common.serialization.Serdes</span><span class="o">;</span>
+
+<span class="kd">final</span> <span class="n">Serde</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="n">stringSerde</span> <span class="o">=</span> <span class="n">Serdes</span><span class="o">.</span><span class="na">String</span><span class="o">();</span>
+<span class="kd">final</span> <span class="n">Serde</span><span class="o">&lt;</span><span class="n">Long</span><span class="o">&gt;</span> <span class="n">longSerde</span> <span class="o">=</span> <span class="n">Serdes</span><span class="o">.</span><span class="na">Long</span><span class="o">();</span>
+
+<span class="c1">// The stream userCountByRegion has type `String` for record keys (for region)</span>
+<span class="c1">// and type `Long` for record values (for user counts).</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">userCountByRegion</span> <span class="o">=</span> <span class="o">...;</span>
+<span class="n">userCountByRegion</span><span class="o">.</span><span class="na">to</span><span class="o">(</span><span class="s">&quot;RegionCountsTopic&quot;</span><span class="o">,</span> <span class="n">Produced</span><span class="o">.</span><span class="na">with</span><span class="o">(</span><span class="n">stringSerde</span><span class="o">,</span> <span class="n">longSerde</span><span class="o">));</span>
+</pre></div>
+      </div>
+      <p>If you want to override serdes selectively, i.e., keep the defaults for some fields, then don&#8217;t specify the serde whenever you want to leverage the default settings:</p>
+      <div class="highlight-java"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">org.apache.kafka.common.serialization.Serde</span><span class="o">;</span>
+<span class="kn">import</span> <span class="nn">org.apache.kafka.common.serialization.Serdes</span><span class="o">;</span>
+
+<span class="c1">// Use the default serializer for record keys (here: region as String) by not specifying the key serde,</span>
+<span class="c1">// but override the default serializer for record values (here: userCount as Long).</span>
+<span class="kd">final</span> <span class="n">Serde</span><span class="o">&lt;</span><span class="n">Long</span><span class="o">&gt;</span> <span class="n">longSerde</span> <span class="o">=</span> <span class="n">Serdes</span><span class="o">.</span><span class="na">Long</span><span class="o">();</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">userCountByRegion</span> <span class="o">=</span> <span class="o">...;</span>
+<span class="n">userCountByRegion</span><span class="o">.</span><span class="na">to</span><span class="o">(</span><span class="s">&quot;RegionCountsTopic&quot;</span><span class="o">,</span> <span class="n">Produced</span><span class="o">.</span><span class="na">valueSerde</span><span class="o">(</span><span class="n">Serdes</span><span class="o">.</span><span class="na">Long</span><span class="o">()));</span>
+</pre></div>
+      </div>
+      <p>If some of your incoming records are corrupted or ill-formatted, they will cause the deserializer class to report an error.
+         Since 1.0.x we have introduced an <code>DeserializationExceptionHandler</code> interface which allows
+         you to customize how to handle such records. The customized implementation of the interface can be specified via the <code>StreamsConfig</code>.
+         For more details, please feel free to read the <a href="config-streams.html#default-deserialization-exception-handler">Configuring a Streams Application</a> section.
+      </p>
+    </div>
+    <div class="section" id="available-serdes">
+      <span id="streams-developer-guide-serdes-available"></span><h2>Available SerDes<a class="headerlink" href="#available-serdes" title="Permalink to this headline"></a></h2>
+      <div class="section" id="primitive-and-basic-types">
+        <h3>Primitive and basic types<a class="headerlink" href="#primitive-and-basic-types" title="Permalink to this headline"></a></h3>
+        <p>Apache Kafka includes several built-in serde implementations for Java primitives and basic types such as <code class="docutils literal"><span class="pre">byte[]</span></code> in
+          its <code class="docutils literal"><span class="pre">kafka-clients</span></code> Maven artifact:</p>
+        <div class="highlight-xml"><div class="highlight"><pre><span></span><span class="nt">&lt;dependency&gt;</span>
+    <span class="nt">&lt;groupId&gt;</span>org.apache.kafka<span class="nt">&lt;/groupId&gt;</span>
+    <span class="nt">&lt;artifactId&gt;</span>kafka-clients<span class="nt">&lt;/artifactId&gt;</span>
+    <span class="nt">&lt;version&gt;</span>{{fullDotVersion}}<span class="nt">&lt;/version&gt;</span>
+<span class="nt">&lt;/dependency&gt;</span>
+</pre></div>
+        </div>
+        <p>This artifact provides the following serde implementations under the package <a class="reference external" href="https://github.com/apache/kafka/blob/1.0/clients/src/main/java/org/apache/kafka/common/serialization">org.apache.kafka.common.serialization</a>, which you can leverage when e.g., defining default serializers in your Streams configuration.</p>
+        <table border="1" class="docutils">
+          <colgroup>
+            <col width="17%" />
+            <col width="83%" />
+          </colgroup>
+          <thead valign="bottom">
+          <tr class="row-odd"><th class="head">Data type</th>
+            <th class="head">Serde</th>
+          </tr>
+          </thead>
+          <tbody valign="top">
+          <tr class="row-even"><td>byte[]</td>
+            <td><code class="docutils literal"><span class="pre">Serdes.ByteArray()</span></code>, <code class="docutils literal"><span class="pre">Serdes.Bytes()</span></code> (see tip below)</td>
+          </tr>
+          <tr class="row-odd"><td>ByteBuffer</td>
+            <td><code class="docutils literal"><span class="pre">Serdes.ByteBuffer()</span></code></td>
+          </tr>
+          <tr class="row-even"><td>Double</td>
+            <td><code class="docutils literal"><span class="pre">Serdes.Double()</span></code></td>
+          </tr>
+          <tr class="row-odd"><td>Integer</td>
+            <td><code class="docutils literal"><span class="pre">Serdes.Integer()</span></code></td>
+          </tr>
+          <tr class="row-even"><td>Long</td>
+            <td><code class="docutils literal"><span class="pre">Serdes.Long()</span></code></td>
+          </tr>
+          <tr class="row-odd"><td>String</td>
+            <td><code class="docutils literal"><span class="pre">Serdes.String()</span></code></td>
+          </tr>
+          <tr class="row-even"><td>UUID</td>
+            <td><code class="docutils literal"><span class="pre">Serdes.UUID()</span></code></td>
+          </tr>
+          </tbody>
+        </table>
+        <div class="admonition tip">
+          <p><b>Tip</b></p>
+          <p class="last"><a class="reference external" href="https://github.com/apache/kafka/blob/1.0/clients/src/main/java/org/apache/kafka/common/utils/Bytes.java">Bytes</a> is a wrapper for Java&#8217;s <code class="docutils literal"><span class="pre">byte[]</span></code> (byte array) that supports proper equality and ordering semantics.  You may want to consider using <code class="docutils literal"><span class="pre">Bytes</span></code> instead of <code class="docutils literal"><span [...]
+        </div>
+      </div>
+      <div class="section" id="json">
+        <h3>JSON<a class="headerlink" href="#json" title="Permalink to this headline"></a></h3>
+        <p>The code examples of Kafka Streams also include a basic serde implementation for JSON:</p>
+        <ul class="simple">
+          <li><a class="reference external" href="https://github.com/apache/kafka/blob/1.0/streams/examples/src/main/java/org/apache/kafka/streams/examples/pageview/JsonPOJOSerializer.java">JsonPOJOSerializer</a></li>
+          <li><a class="reference external" href="https://github.com/apache/kafka/blob/1.0/streams/examples/src/main/java/org/apache/kafka/streams/examples/pageview/JsonPOJODeserializer.java">JsonPOJODeserializer</a></li>
+        </ul>
+        <p>You can construct a unified JSON serde from the <code class="docutils literal"><span class="pre">JsonPOJOSerializer</span></code> and <code class="docutils literal"><span class="pre">JsonPOJODeserializer</span></code> via
+          <code class="docutils literal"><span class="pre">Serdes.serdeFrom(&lt;serializerInstance&gt;,</span> <span class="pre">&lt;deserializerInstance&gt;)</span></code>.  The
+          <a class="reference external" href="https://github.com/apache/kafka/blob/1.0/streams/examples/src/main/java/org/apache/kafka/streams/examples/pageview/PageViewTypedDemo.java">PageViewTypedDemo</a>
+          example demonstrates how to use this JSON serde.</p>
+      </div>
+      <div class="section" id="implementing-custom-serdes">
+        <span id="streams-developer-guide-serdes-custom"></span><h2>Implementing custom SerDes<a class="headerlink" href="#implementing-custom-serdes" title="Permalink to this headline"></a></h2>
+        <p>If you need to implement custom SerDes, your best starting point is to take a look at the source code references of
+          existing SerDes (see previous section).  Typically, your workflow will be similar to:</p>
+        <ol class="arabic simple">
+          <li>Write a <em>serializer</em> for your data type <code class="docutils literal"><span class="pre">T</span></code> by implementing
+            <a class="reference external" href="https://github.com/apache/kafka/blob/1.0/clients/src/main/java/org/apache/kafka/common/serialization/Serializer.java">org.apache.kafka.common.serialization.Serializer</a>.</li>
+          <li>Write a <em>deserializer</em> for <code class="docutils literal"><span class="pre">T</span></code> by implementing
+            <a class="reference external" href="https://github.com/apache/kafka/blob/1.0/clients/src/main/java/org/apache/kafka/common/serialization/Deserializer.java">org.apache.kafka.common.serialization.Deserializer</a>.</li>
+          <li>Write a <em>serde</em> for <code class="docutils literal"><span class="pre">T</span></code> by implementing
+            <a class="reference external" href="https://github.com/apache/kafka/blob/1.0/clients/src/main/java/org/apache/kafka/common/serialization/Serde.java">org.apache.kafka.common.serialization.Serde</a>,
+            which you either do manually (see existing SerDes in the previous section) or by leveraging helper functions in
+            <a class="reference external" href="https://github.com/apache/kafka/blob/1.0/clients/src/main/java/org/apache/kafka/common/serialization/Serdes.java">Serdes</a>
+            such as <code class="docutils literal"><span class="pre">Serdes.serdeFrom(Serializer&lt;T&gt;, Deserializer&lt;T&gt;)</span></code>.
+            Note that you will need to implement your own class (that has no generic types) if you want to use your custom serde in the configuration provided to <code class="docutils literal"><span class="pre">KafkaStreams</span></code>.
+            If your serde class has generic types or you use <code class="docutils literal"><span class="pre">Serdes.serdeFrom(Serializer&lt;T&gt;, Deserializer&lt;T&gt;)</span></code>, you can pass your serde only
+            via methods calls (for example <code class="docutils literal"><span class="pre">builder.stream("topicName", Consumed.with(...))</span></code>).</li>
+        </ol>
+      </div>
+    </div>
+    <div class="section" id="scala-dsl-serdes">
+      <h2>Kafka Streams DSL for Scala Implicit SerDes<a class="headerlink" href="scala-dsl-serdes" title="Permalink to this headline"></a></h2>
+      <p>When using the <a href="dsl-api.html#scala-dsl">Kafka Streams DSL for Scala</a> you're not required to configure a default SerDes.  In fact, it's not supported.  SerDes are instead provided implicitly by default implementations for common primitive datatypes.  See the <a href="dsl-api.html#scala-dsl-implicit-serdes">Implicit SerDes</a> and <a href="dsl-api.html#scala-dsl-user-defined-serdes">User-Defined SerDes</a> sections in the DSL API documentation for details</p>
+    </div>
+
+
+               </div>
+              </div>
+  <div class="pagination">
+    <a href="/{{version}}/documentation/streams/developer-guide/processor-api" class="pagination__btn pagination__btn__prev">Previous</a>
+    <a href="/{{version}}/documentation/streams/developer-guide/testing" class="pagination__btn pagination__btn__next">Next</a>
+  </div>
+</script>
+
+<!--#include virtual="../../../includes/_header.htm" -->
+<!--#include virtual="../../../includes/_top.htm" -->
+<div class="content documentation documentation--current">
+  <!--#include virtual="../../../includes/_nav.htm" -->
+  <div class="right">
+    <!--#include virtual="../../../includes/_docs_banner.htm" -->
+    <ul class="breadcrumbs">
+      <li><a href="/documentation">Documentation</a></li>
+      <li><a href="/documentation/streams">Kafka Streams</a></li>
+      <li><a href="/documentation/streams/developer-guide/">Developer Guide</a></li>
+    </ul>
+    <div class="p-content"></div>
+  </div>
+</div>
+<!--#include virtual="../../../includes/_footer.htm" -->
+<script>
+    $(function() {
+        // Show selected style on nav item
+        $('.b-nav__streams').addClass('selected');
+
+        //sticky secondary nav
+        var $navbar = $(".sub-nav-sticky"),
+            y_pos = $navbar.offset().top,
+            height = $navbar.height();
+
+        $(window).scroll(function() {
+            var scrollTop = $(window).scrollTop();
+
+            if (scrollTop > y_pos - height) {
+                $navbar.addClass("navbar-fixed")
+            } else if (scrollTop <= y_pos) {
+                $navbar.removeClass("navbar-fixed")
+            }
+        });
+
+        // Display docs subnav items
+        $('.b-nav__docs').parent().toggleClass('nav__item__with__subs--expanded');
+    });
+</script>
diff --git a/21/streams/developer-guide/dsl-api.html b/21/streams/developer-guide/dsl-api.html
new file mode 100644
index 0000000..4b91eaa
--- /dev/null
+++ b/21/streams/developer-guide/dsl-api.html
@@ -0,0 +1,3601 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<script><!--#include virtual="../../js/templateData.js" --></script>
+
+<script id="content-template" type="text/x-handlebars-template">
+  <!-- h1>Developer Guide for Kafka Streams</h1 -->
+  <!-- div class="sub-nav-sticky">
+    <div class="sticky-top">
+      <div style="height:35px">
+        <a href="/{{version}}/documentation/streams/">Introduction</a>
+        <a class="active-menu-item" href="/{{version}}/documentation/streams/developer-guide">Developer Guide</a>
+        <a href="/{{version}}/documentation/streams/core-concepts">Concepts</a>
+        <a href="/{{version}}/documentation/streams/quickstart">Run Demo App</a>
+        <a href="/{{version}}/documentation/streams/tutorial">Tutorial: Write App</a>
+      </div>
+    </div -->
+  </div>
+
+
+    <div class="section" id="streams-dsl">
+        <span id="streams-developer-guide-dsl"></span><h1>Streams DSL<a class="headerlink" href="#streams-dsl" title="Permalink to this headline"></a></h1>
+        <p>The Kafka Streams DSL (Domain Specific Language) is built on top of the Streams Processor API. It is the recommended for
+            most users, especially beginners. Most data processing operations can be expressed in just a few lines of DSL code.</p>
+        <div class="contents local topic" id="table-of-contents">
+            <p class="topic-title first"><b>Table of Contents</b></p>
+            <ul class="simple">
+                <li><a class="reference internal" href="#overview" id="id7">Overview</a></li>
+                <li><a class="reference internal" href="#creating-source-streams-from-kafka" id="id8">Creating source streams from Kafka</a></li>
+                <li><a class="reference internal" href="#transform-a-stream" id="id9">Transform a stream</a><ul>
+                    <li><a class="reference internal" href="#stateless-transformations" id="id10">Stateless transformations</a></li>
+                    <li><a class="reference internal" href="#stateful-transformations" id="id11">Stateful transformations</a><ul>
+                        <li><a class="reference internal" href="#aggregating" id="id12">Aggregating</a></li>
+                        <li><a class="reference internal" href="#joining" id="id13">Joining</a><ul>
+                            <li><a class="reference internal" href="#join-co-partitioning-requirements" id="id14">Join co-partitioning requirements</a></li>
+                            <li><a class="reference internal" href="#kstream-kstream-join" id="id15">KStream-KStream Join</a></li>
+                            <li><a class="reference internal" href="#ktable-ktable-join" id="id16">KTable-KTable Join</a></li>
+                            <li><a class="reference internal" href="#kstream-ktable-join" id="id17">KStream-KTable Join</a></li>
+                            <li><a class="reference internal" href="#kstream-globalktable-join" id="id18">KStream-GlobalKTable Join</a></li>
+                        </ul>
+                        </li>
+                        <li><a class="reference internal" href="#windowing" id="id19">Windowing</a><ul>
+                            <li><a class="reference internal" href="#tumbling-time-windows" id="id20">Tumbling time windows</a></li>
+                            <li><a class="reference internal" href="#hopping-time-windows" id="id21">Hopping time windows</a></li>
+                            <li><a class="reference internal" href="#sliding-time-windows" id="id22">Sliding time windows</a></li>
+                            <li><a class="reference internal" href="#session-windows" id="id23">Session Windows</a></li>
+                            <li><a class="reference internal" href="#window-final-results" id="id31">Window Final Results</a></li>
+                        </ul>
+                        </li>
+                    </ul>
+                    </li>
+                    <li><a class="reference internal" href="#applying-processors-and-transformers-processor-api-integration" id="id24">Applying processors and transformers (Processor API integration)</a></li>
+                </ul>
+                </li>
+                <li><a class="reference internal" href="#controlling-emit-rate" id="id32">Controlling KTable update rate</a></li>
+                <li><a class="reference internal" href="#writing-streams-back-to-kafka" id="id25">Writing streams back to Kafka</a></li>
+                <li><a class="reference internal" href="#testing-a-streams-app" id="id26">Testing a Streams application</a></li>
+                <li><a class="reference internal" href="#scala-dsl" id="id27">Kafka Streams DSL for Scala</a></li>
+                <ul>
+                    <li><a class="reference internal" href="#scala-dsl-sample-usage" id="id28">Sample Usage</a></li>
+                    <li><a class="reference internal" href="#scala-dsl-implicit-serdes" id="id29">Implicit SerDes</a></li>
+                    <li><a class="reference internal" href="#scala-dsl-user-defined-serdes" id="id30">User-Defined SerDes</a></li>
+                </ul>
+            </ul>
+        </div>
+        <div class="section" id="overview">
+            <h2><a class="toc-backref" href="#id7">Overview</a><a class="headerlink" href="#overview" title="Permalink to this headline"></a></h2>
+            <p>In comparison to the <a class="reference internal" href="processor-api.html#streams-developer-guide-processor-api"><span class="std std-ref">Processor API</span></a>, only the DSL supports:</p>
+            <ul class="simple">
+                <li>Built-in abstractions for <a class="reference internal" href="../core-concepts.html#streams_concepts_duality"><span class="std std-ref">streams and tables</span></a> in the form of
+                    <a class="reference internal" href="#streams_concepts_kstream"><span class="std std-ref">KStream</span></a>, <a class="reference internal" href="#streams_concepts_ktable"><span class="std std-ref">KTable</span></a>, and
+                    <a class="reference internal" href="#streams_concepts_globalktable"><span class="std std-ref">GlobalKTable</span></a>.  Having first-class support for streams and tables is crucial
+                    because, in practice, most use cases require not just either streams or databases/tables, but a combination of both.
+                    For example, if your use case is to create a customer 360-degree view that is updated in real-time, what your
+                    application will be doing is transforming many input <em>streams</em> of customer-related events into an output <em>table</em>
+                    that contains a continuously updated 360-degree view of your customers.</li>
+                <li>Declarative, functional programming style with
+                    <a class="reference internal" href="#streams-developer-guide-dsl-transformations-stateless"><span class="std std-ref">stateless transformations</span></a> (e.g. <code class="docutils literal"><span class="pre">map</span></code> and <code class="docutils literal"><span class="pre">filter</span></code>)
+                    as well as <a class="reference internal" href="#streams-developer-guide-dsl-transformations-stateful"><span class="std std-ref">stateful transformations</span></a>
+                    such as <a class="reference internal" href="#streams-developer-guide-dsl-aggregating"><span class="std std-ref">aggregations</span></a> (e.g. <code class="docutils literal"><span class="pre">count</span></code> and <code class="docutils literal"><span class="pre">reduce</span></code>),
+                    <a class="reference internal" href="#streams-developer-guide-dsl-joins"><span class="std std-ref">joins</span></a> (e.g. <code class="docutils literal"><span class="pre">leftJoin</span></code>), and
+                    <a class="reference internal" href="#streams-developer-guide-dsl-windowing"><span class="std std-ref">windowing</span></a> (e.g. <a class="reference internal" href="#windowing-session"><span class="std std-ref">session windows</span></a>).</li>
+            </ul>
+            <p>With the DSL, you can define <a class="reference internal" href="../core-concepts.html#streams_topology"><span class="std std-ref">processor topologies</span></a> (i.e., the logical
+                processing plan) in your application. The steps to accomplish this are:</p>
+            <ol class="arabic simple">
+                <li>Specify <a class="reference internal" href="#streams-developer-guide-dsl-sources"><span class="std std-ref">one or more input streams that are read from Kafka topics</span></a>.</li>
+                <li>Compose <a class="reference internal" href="#streams-developer-guide-dsl-transformations"><span class="std std-ref">transformations</span></a> on these streams.</li>
+                <li>Write the <a class="reference internal" href="#streams-developer-guide-dsl-destinations"><span class="std std-ref">resulting output streams back to Kafka topics</span></a>, or expose the processing results of your application directly to other applications through <a class="reference internal" href="interactive-queries.html#streams-developer-guide-interactive-queries"><span class="std std-ref">interactive queries</span></a> (e.g., via a REST API).</li>
+            </ol>
+            <p>After the application is run, the defined processor topologies are continuously executed (i.e., the processing plan is put into
+                action). A step-by-step guide for writing a stream processing application using the DSL is provided below.</p>
+            <p>For a complete list of available API functionality, see also the <a href="../../../javadoc/org/apache/kafka/streams/package-summary.html">Streams</a> API docs.</p>
+        </div>
+
+        <div class="section" id="dsl-core-constructs-overview">
+            <h4><a id="streams_concepts_kstream" href="#streams_concepts_kstream">KStream</a></h4>
+
+            <p>
+                Only the <strong>Kafka Streams DSL</strong> has the notion of a <code>KStream</code>.
+            </p>
+
+            <p>
+                A <strong>KStream</strong> is an abstraction of a <strong>record stream</strong>, where each data record represents a self-contained datum in the unbounded data set. Using the table analogy, data records in a record stream are always interpreted as an &quot;INSERT&quot; -- think: adding more entries to an append-only ledger -- because no record replaces an existing row with the same key. Examples are a credit card transaction, a page view event, or a server log entry.
+            </p>
+
+            <p>
+                To illustrate, let's imagine the following two data records are being sent to the stream:
+            </p>
+
+            <div class="sourcecode">
+                <p>(&quot;alice&quot;, 1) --&gt; (&quot;alice&quot;, 3)</p>
+            </div>
+
+            <p>
+                If your stream processing application were to sum the values per user, it would return <code>4</code> for <code>alice</code>. Why? Because the second data record would not be considered an update of the previous record. Compare this behavior of KStream to <code>KTable</code> below,
+                which would return <code>3</code> for <code>alice</code>.
+            </p>
+
+            <h4><a id="streams_concepts_ktable" href="#streams_concepts_ktable">KTable</a></h4>
+
+            <p>
+                Only the <strong>Kafka Streams DSL</strong> has the notion of a <code>KTable</code>.
+            </p>
+
+            <p>
+                A <strong>KTable</strong> is an abstraction of a <strong>changelog stream</strong>, where each data record represents an update. More precisely, the value in a data record is interpreted as an &quot;UPDATE&quot; of the last value for the same record key, if any (if a corresponding key doesn't exist yet, the update will be considered an INSERT). Using the table analogy, a data record in a changelog stream is interpreted as an UPSERT aka INSERT/UPDATE because any existing r [...]
+            </p>
+            <p>
+                To illustrate, let's imagine the following two data records are being sent to the stream:
+            </p>
+
+            <div class="sourcecode">
+                <p>
+                    (&quot;alice&quot;, 1) --&gt; (&quot;alice&quot;, 3)
+                </p>
+            </div>
+
+            <p>
+                If your stream processing application were to sum the values per user, it would return <code>3</code> for <code>alice</code>. Why? Because the second data record would be considered an update of the previous record.
+            </p>
+
+            <p>
+                <strong>Effects of Kafka's log compaction:</strong> Another way of thinking about KStream and KTable is as follows: If you were to store a KTable into a Kafka topic, you'd probably want to enable Kafka's <a href="http://kafka.apache.org/documentation.html#compaction">log compaction</a> feature, e.g. to save storage space.
+            </p>
+
+            <p>
+                However, it would not be safe to enable log compaction in the case of a KStream because, as soon as log compaction would begin purging older data records of the same key, it would break the semantics of the data. To pick up the illustration example again, you'd suddenly get a <code>3</code> for <code>alice</code> instead of a <code>4</code> because log compaction would have removed the <code>(&quot;alice&quot;, 1)</code> data record. Hence log compaction is perfectly safe [...]
+            </p>
+
+            <p>
+                We have already seen an example of a changelog stream in the section <strong>streams_concepts_duality</strong>. Another example are change data capture (CDC) records in the changelog of a relational database, representing which row in a database table was inserted, updated, or deleted.
+            </p>
+
+            <p>
+                KTable also provides an ability to look up <em>current</em> values of data records by keys. This table-lookup functionality is available through <strong>join operations</strong> (see also <strong>Joining</strong> in the Developer Guide) as well as through <strong>Interactive Queries</strong>.
+            </p>
+
+            <h4><a id="streams_concepts_globalktable" href="#streams_concepts_globalktable">GlobalKTable</a></h4>
+
+            <p>Only the <strong>Kafka Streams DSL</strong> has the notion of a <strong>GlobalKTable</strong>.</p>
+
+            <p>
+                Like a <strong>KTable</strong>, a <strong>GlobalKTable</strong> is an abstraction of a <strong>changelog stream</strong>, where each data record represents an update.
+            </p>
+
+            <p>
+                A GlobalKTable differs from a KTable in the data that they are being populated with, i.e. which data from the underlying Kafka topic is being read into the respective table. Slightly simplified, imagine you have an input topic with 5 partitions. In your application, you want to read this topic into a table. Also, you want to run your application across 5 application instances for <strong>maximum parallelism</strong>.
+            </p>
+
+            <ul>
+                <li>
+                    If you read the input topic into a <strong>KTable</strong>, then the &quot;local&quot; KTable instance of each application instance will be populated with data <strong>from only 1 partition</strong> of the topic's 5 partitions.
+                </li>
+
+                <li>
+                    If you read the input topic into a <strong>GlobalKTable</strong>, then the local GlobalKTable instance of each application instance will be populated with data <strong>from all partitions of the topic</strong>.
+                </li>
+            </ul>
+
+            <p>
+                GlobalKTable provides the ability to look up <em>current</em> values of data records by keys. This table-lookup functionality is available through <code class="interpreted-text">join operations</code>.
+            </p>
+            <p>Benefits of global tables:</p>
+
+            <ul>
+                <li>
+                    More convenient and/or efficient <strong>joins</strong>: Notably, global tables allow you to perform star joins, they support &quot;foreign-key&quot; lookups (i.e., you can lookup data in the table not just by record key, but also by data in the record values), and they are more efficient when chaining multiple joins. Also, when joining against a global table, the input data does not need to be <strong>co-partitioned</strong>.
+                </li>
+                <li>
+                    Can be used to &quot;broadcast&quot; information to all the running instances of your application.
+                </li>
+            </ul>
+
+            <p>Downsides of global tables:</p>
+            <ul>
+                <li>Increased local storage consumption compared to the (partitioned) KTable because the entire topic is tracked.</li>
+                <li>Increased network and Kafka broker load compared to the (partitioned) KTable because the entire topic is read.</li>
+            </ul>
+
+        </div>
+        <div class="section" id="creating-source-streams-from-kafka">
+            <span id="streams-developer-guide-dsl-sources"></span><h2><a class="toc-backref" href="#id8">Creating source streams from Kafka</a><a class="headerlink" href="#creating-source-streams-from-kafka" title="Permalink to this headline"></a></h2>
+            <p>You can easily read data from Kafka topics into your application.  The following operations are supported.</p>
+            <table border="1" class="non-scrolling-table width-100-percent docutils">
+                <colgroup>
+                    <col width="22%" />
+                    <col width="78%" />
+                </colgroup>
+                <thead valign="bottom">
+                <tr class="row-odd"><th class="head">Reading from Kafka</th>
+                    <th class="head">Description</th>
+                </tr>
+                </thead>
+                <tbody valign="top">
+                <tr class="row-even"><td><p class="first"><strong>Stream</strong></p>
+                    <ul class="last simple">
+                        <li><em>input topics</em> &rarr; KStream</li>
+                    </ul>
+                </td>
+                    <td><p class="first">Creates a <a class="reference internal" href="#streams_concepts_kstream"><span class="std std-ref">KStream</span></a> from the specified Kafka input topics and interprets the data
+                        as a <a class="reference internal" href="#streams_concepts_kstream"><span class="std std-ref">record stream</span></a>.
+                        A <code class="docutils literal"><span class="pre">KStream</span></code> represents a <em>partitioned</em> record stream.
+                        <a class="reference external" href="../../../javadoc/org/apache/kafka/streams/StreamsBuilder.html#stream(java.lang.String)">(details)</a></p>
+                        <p>In the case of a KStream, the local KStream instance of every application instance will
+                            be populated with data from only <strong>a subset</strong> of the partitions of the input topic.  Collectively, across
+                            all application instances, all input topic partitions are read and processed.</p>
+                        <div class="highlight-java"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">org.apache.kafka.common.serialization.Serdes</span><span class="o">;</span>
+<span class="kn">import</span> <span class="nn">org.apache.kafka.streams.StreamsBuilder</span><span class="o">;</span>
+<span class="kn">import</span> <span class="nn">org.apache.kafka.streams.kstream.KStream</span><span class="o">;</span>
+
+<span class="n">StreamsBuilder</span> <span class="n">builder</span> <span class="o">=</span> <span class="k">new</span> <span class="n">StreamsBuilder</span><span class="o">();</span>
+
+<span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">wordCounts</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">stream</span><span class="o">(</span>
+    <span class="s">&quot;word-counts-input-topic&quot;</span><span class="o">,</span> <span class="cm">/* input topic */</span>
+    <span class="n">Consumed</span><span class="o">.</span><span class="na">with</span><span class="o">(</span>
+      <span class="n">Serdes</span><span class="o">.</span><span class="na">String</span><span class="o">(),</span> <span class="cm">/* key serde */</span>
+      <span class="n">Serdes</span><span class="o">.</span><span class="na">Long</span><span class="o">()</span>   <span class="cm">/* value serde */</span>
+    <span class="o">);</span>
+</pre></div>
+                        </div>
+                        <p>If you do not specify SerDes explicitly, the default SerDes from the
+                            <a class="reference internal" href="config-streams.html#streams-developer-guide-configuration"><span class="std std-ref">configuration</span></a> are used.</p>
+                        <p>You <strong>must specify SerDes explicitly</strong> if the key or value types of the records in the Kafka input
+                            topics do not match the configured default SerDes. For information about configuring default SerDes, available
+                            SerDes, and implementing your own custom SerDes see <a class="reference internal" href="datatypes.html#streams-developer-guide-serdes"><span class="std std-ref">Data Types and Serialization</span></a>.</p>
+                        <p class="last">Several variants of <code class="docutils literal"><span class="pre">stream</span></code> exist, for example to specify a regex pattern for input topics to read from).</p>
+                    </td>
+                </tr>
+                <tr class="row-odd"><td><p class="first"><strong>Table</strong></p>
+                    <ul class="last simple">
+                        <li><em>input topic</em> &rarr; KTable</li>
+                    </ul>
+                </td>
+                    <td><p class="first">Reads the specified Kafka input topic into a <a class="reference internal" href="#streams_concepts_ktable"><span class="std std-ref">KTable</span></a>.  The topic is
+                        interpreted as a changelog stream, where records with the same key are interpreted as UPSERT aka INSERT/UPDATE
+                        (when the record value is not <code class="docutils literal"><span class="pre">null</span></code>) or as DELETE (when the value is <code class="docutils literal"><span class="pre">null</span></code>) for that key.
+                        <a class="reference external" href="../../../javadoc/org/apache/kafka/streams/StreamsBuilder.html#table-java.lang.String(java.lang.String)">(details)</a></p>
+                        <p>In the case of a KTable, the local KTable instance of every application instance will
+                            be populated with data from only <strong>a subset</strong> of the partitions of the input topic.  Collectively, across
+                            all application instances, all input topic partitions are read and processed.</p>
+                        <p>You must provide a name for the table (more precisely, for the internal
+                            <a class="reference internal" href="../architecture.html#streams_architecture_state"><span class="std std-ref">state store</span></a> that backs the table).  This is required for
+                            supporting <a class="reference internal" href="interactive-queries.html#streams-developer-guide-interactive-queries"><span class="std std-ref">interactive queries</span></a> against the table. When a
+                            name is not provided the table will not queryable and an internal name will be provided for the state store.</p>
+                        <p>If you do not specify SerDes explicitly, the default SerDes from the
+                            <a class="reference internal" href="config-streams.html#streams-developer-guide-configuration"><span class="std std-ref">configuration</span></a> are used.</p>
+                        <p>You <strong>must specify SerDes explicitly</strong> if the key or value types of the records in the Kafka input
+                            topics do not match the configured default SerDes. For information about configuring default SerDes, available
+                            SerDes, and implementing your own custom SerDes see <a class="reference internal" href="datatypes.html#streams-developer-guide-serdes"><span class="std std-ref">Data Types and Serialization</span></a>.</p>
+                        <p class="last">Several variants of <code class="docutils literal"><span class="pre">table</span></code> exist, for example to specify the <code class="docutils literal"><span class="pre">auto.offset.reset</span></code> policy to be used when
+                            reading from the input topic.</p>
+                    </td>
+                </tr>
+                <tr class="row-even"><td><p class="first"><strong>Global Table</strong></p>
+                    <ul class="last simple">
+                        <li><em>input topic</em> &rarr; GlobalKTable</li>
+                    </ul>
+                </td>
+                    <td><p class="first">Reads the specified Kafka input topic into a <a class="reference internal" href="#streams_concepts_globalktable"><span class="std std-ref">GlobalKTable</span></a>.  The topic is
+                        interpreted as a changelog stream, where records with the same key are interpreted as UPSERT aka INSERT/UPDATE
+                        (when the record value is not <code class="docutils literal"><span class="pre">null</span></code>) or as DELETE (when the value is <code class="docutils literal"><span class="pre">null</span></code>) for that key.
+                        <a class="reference external" href="../../../javadoc/org/apache/kafka/streams/StreamsBuilder.html#globalTable-java.lang.String(java.lang.String)">(details)</a></p>
+                        <p>In the case of a GlobalKTable, the local GlobalKTable instance of every application instance will
+                            be populated with data from <strong>all</strong> the partitions of the input topic.</p>
+                        <p>You must provide a name for the table (more precisely, for the internal
+                            <a class="reference internal" href="../architecture.html#streams_architecture_state"><span class="std std-ref">state store</span></a> that backs the table).  This is required for
+                            supporting <a class="reference internal" href="interactive-queries.html#streams-developer-guide-interactive-queries"><span class="std std-ref">interactive queries</span></a> against the table. When a
+                            name is not provided the table will not queryable and an internal name will be provided for the state store.</p>
+                        <div class="highlight-java"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">org.apache.kafka.common.serialization.Serdes</span><span class="o">;</span>
+<span class="kn">import</span> <span class="nn">org.apache.kafka.streams.StreamsBuilder</span><span class="o">;</span>
+<span class="kn">import</span> <span class="nn">org.apache.kafka.streams.kstream.GlobalKTable</span><span class="o">;</span>
+
+<span class="n">StreamsBuilder</span> <span class="n">builder</span> <span class="o">=</span> <span class="k">new</span> <span class="n">StreamsBuilder</span><span class="o">();</span>
+
+<span class="n">GlobalKTable</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">wordCounts</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">globalTable</span><span class="o">(</span>
+    <span class="s">&quot;word-counts-input-topic&quot;</span><span class="o">,</span>
+    <span class="n">Materialized</span><span class="o">.&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">,</span> <span class="n">KeyValueStore</span><span class="o">&lt;</span><span class="n">Bytes</span><span class="o">,</span> <span class="kt">byte</span><span class="o">[]&gt;&gt;</span><span class="n">as</span><span class="o">(</span>
+      <span class="s">&quot;word-counts-global-store&quot;</span> <span class="cm">/* table/store name */</span><span class="o">)</span>
+      <span class="o">.</span><span class="na">withKeySerde</span><span class="o">(</span><span class="n">Serdes</span><span class="o">.</span><span class="na">String</span><span class="o">())</span> <span class="cm">/* key serde */</span>
+      <span class="o">.</span><span class="na">withValueSerde</span><span class="o">(</span><span class="n">Serdes</span><span class="o">.</span><span class="na">Long</span><span class="o">())</span> <span class="cm">/* value serde */</span>
+    <span class="o">);</span>
+</pre></div>
+                        </div>
+                        <p>You <strong>must specify SerDes explicitly</strong> if the key or value types of the records in the Kafka input
+                            topics do not match the configured default SerDes. For information about configuring default SerDes, available
+                            SerDes, and implementing your own custom SerDes see <a class="reference internal" href="datatypes.html#streams-developer-guide-serdes"><span class="std std-ref">Data Types and Serialization</span></a>.</p>
+                        <p class="last">Several variants of <code class="docutils literal"><span class="pre">globalTable</span></code> exist to e.g. specify explicit SerDes.</p>
+                    </td>
+                </tr>
+                </tbody>
+            </table>
+        </div>
+        <div class="section" id="transform-a-stream">
+            <span id="streams-developer-guide-dsl-transformations"></span><h2><a class="toc-backref" href="#id9">Transform a stream</a><a class="headerlink" href="#transform-a-stream" title="Permalink to this headline"></a></h2>
+            <p>The KStream and KTable interfaces support a variety of transformation operations.
+                Each of these operations can be translated into one or more connected processors into the underlying processor topology.
+                Since KStream and KTable are strongly typed, all of these transformation operations are defined as
+                generic functions where users could specify the input and output data types.</p>
+            <p>Some KStream transformations may generate one or more KStream objects, for example:
+                - <code class="docutils literal"><span class="pre">filter</span></code> and <code class="docutils literal"><span class="pre">map</span></code> on a KStream will generate another KStream
+                - <code class="docutils literal"><span class="pre">branch</span></code> on KStream can generate multiple KStreams</p>
+            <p>Some others may generate a KTable object, for example an aggregation of a KStream also yields a KTable. This allows Kafka Streams to continuously update the computed value upon arrivals of <a class="reference internal" href="../core-concepts.html#streams_concepts_aggregations"><span class="std std-ref">late records</span></a> after it
+                has already been produced to the downstream transformation operators.</p>
+            <p>All KTable transformation operations can only generate another KTable. However, the Kafka Streams DSL does provide a special function
+                that converts a KTable representation into a KStream. All of these transformation methods can be chained together to compose
+                a complex processor topology.</p>
+            <p>These transformation operations are described in the following subsections:</p>
+            <ul class="simple">
+                <li><a class="reference internal" href="#streams-developer-guide-dsl-transformations-stateless"><span class="std std-ref">Stateless transformations</span></a></li>
+                <li><a class="reference internal" href="#streams-developer-guide-dsl-transformations-stateful"><span class="std std-ref">Stateful transformations</span></a></li>
+            </ul>
+            <div class="section" id="stateless-transformations">
+                <span id="streams-developer-guide-dsl-transformations-stateless"></span><h3><a class="toc-backref" href="#id10">Stateless transformations</a><a class="headerlink" href="#stateless-transformations" title="Permalink to this headline"></a></h3>
+                <p>Stateless transformations do not require state for processing and they do not require a state store associated with
+                    the stream processor. Kafka 0.11.0 and later allows you to materialize the result from a stateless <code class="docutils literal"><span class="pre">KTable</span></code> transformation. This allows the result to be queried through <a class="reference internal" href="interactive-queries.html#streams-developer-guide-interactive-queries"><span class="std std-ref">interactive queries</span></a>. To materialize a <code class="docutils literal"><span class="pre">KTable</span [...]
+                <table border="1" class="non-scrolling-table width-100-percent docutils">
+                    <colgroup>
+                        <col width="22%" />
+                        <col width="78%" />
+                    </colgroup>
+                    <thead valign="bottom">
+                    <tr class="row-odd"><th class="head">Transformation</th>
+                        <th class="head">Description</th>
+                    </tr>
+                    </thead>
+                    <tbody valign="top">
+                    <tr class="row-even"><td><p class="first"><strong>Branch</strong></p>
+                        <ul class="last simple">
+                            <li>KStream &rarr; KStream[]</li>
+                        </ul>
+                    </td>
+                        <td><p class="first">Branch (or split) a <code class="docutils literal"><span class="pre">KStream</span></code> based on the supplied predicates into one or more <code class="docutils literal"><span class="pre">KStream</span></code> instances.
+                            (<a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KStream.html#branch-org.apache.kafka.streams.kstream.Predicate...-">details</a>)</p>
+                            <p>Predicates are evaluated in order.  A record is placed to one and only one output stream on the first match:
+                                if the n-th predicate evaluates to true, the record is placed to n-th stream.  If no predicate matches, the
+                                the record is dropped.</p>
+                            <p>Branching is useful, for example, to route records to different downstream topics.</p>
+                            <div class="last highlight-java"><div class="highlight"><pre><span></span><span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">stream</span> <span class="o">=</span> <span class="o">...;</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;[]</span> <span class="n">branches</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">branch</span><span class="o">(</span>
+    <span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">key</span><span class="o">.</span><span class="na">startsWith</span><span class="o">(</span><span class="s">&quot;A&quot;</span><span class="o">),</span> <span class="cm">/* first predicate  */</span>
+    <span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">key</span><span class="o">.</span><span class="na">startsWith</span><span class="o">(</span><span class="s">&quot;B&quot;</span><span class="o">),</span> <span class="cm">/* second predicate */</span>
+    <span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="kc">true</span>                 <span class="cm">/* third predicate  */</span>
+  <span class="o">);</span>
+
+<span class="c1">// KStream branches[0] contains all records whose keys start with &quot;A&quot;</span>
+<span class="c1">// KStream branches[1] contains all records whose keys start with &quot;B&quot;</span>
+<span class="c1">// KStream branches[2] contains all other records</span>
+
+<span class="c1">// Java 7 example: cf. `filter` for how to create `Predicate` instances</span>
+</pre></div>
+                            </div>
+                        </td>
+                    </tr>
+                    <tr class="row-odd"><td><p class="first"><strong>Filter</strong></p>
+                        <ul class="last simple">
+                            <li>KStream &rarr; KStream</li>
+                            <li>KTable &rarr; KTable</li>
+                        </ul>
+                    </td>
+                        <td><p class="first">Evaluates a boolean function for each element and retains those for which the function returns true.
+                            (<a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KStream.html#filter-org.apache.kafka.streams.kstream.Predicate-">KStream details</a>,
+                            <a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KTable.html#filter-org.apache.kafka.streams.kstream.Predicate-">KTable details</a>)</p>
+                            <div class="last highlight-java"><div class="highlight"><pre><span></span><span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">stream</span> <span class="o">=</span> <span class="o">...;</span>
+
+<span class="c1">// A filter that selects (keeps) only positive numbers</span>
+<span class="c1">// Java 8+ example, using lambda expressions</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">onlyPositives</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">filter</span><span class="o">((</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">value</ [...]
+
+<span class="c1">// Java 7 example</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">onlyPositives</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">filter</span><span class="o">(</span>
+    <span class="k">new</span> <span class="n">Predicate</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;()</span> <span class="o">{</span>
+      <span class="nd">@Override</span>
+      <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">test</span><span class="o">(</span><span class="n">String</span> <span class="n">key</span><span class="o">,</span> <span class="n">Long</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
+        <span class="k">return</span> <span class="n">value</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">;</span>
+      <span class="o">}</span>
+    <span class="o">});</span>
+</pre></div>
+                            </div>
+                        </td>
+                    </tr>
+                    <tr class="row-even"><td><p class="first"><strong>Inverse Filter</strong></p>
+                        <ul class="last simple">
+                            <li>KStream &rarr; KStream</li>
+                            <li>KTable &rarr; KTable</li>
+                        </ul>
+                    </td>
+                        <td><p class="first">Evaluates a boolean function for each element and drops those for which the function returns true.
+                            (<a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KStream.html#filterNot-org.apache.kafka.streams.kstream.Predicate-">KStream details</a>,
+                            <a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KTable.html#filterNot-org.apache.kafka.streams.kstream.Predicate-">KTable details</a>)</p>
+                            <div class="last highlight-java"><div class="highlight"><pre><span></span><span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">stream</span> <span class="o">=</span> <span class="o">...;</span>
+
+<span class="c1">// An inverse filter that discards any negative numbers or zero</span>
+<span class="c1">// Java 8+ example, using lambda expressions</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">onlyPositives</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">filterNot</span><span class="o">((</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">valu [...]
+
+<span class="c1">// Java 7 example</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">onlyPositives</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">filterNot</span><span class="o">(</span>
+    <span class="k">new</span> <span class="n">Predicate</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;()</span> <span class="o">{</span>
+      <span class="nd">@Override</span>
+      <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">test</span><span class="o">(</span><span class="n">String</span> <span class="n">key</span><span class="o">,</span> <span class="n">Long</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
+        <span class="k">return</span> <span class="n">value</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="o">;</span>
+      <span class="o">}</span>
+    <span class="o">});</span>
+</pre></div>
+                            </div>
+                        </td>
+                    </tr>
+                    <tr class="row-odd"><td><p class="first"><strong>FlatMap</strong></p>
+                        <ul class="last simple">
+                            <li>KStream &rarr; KStream</li>
+                        </ul>
+                    </td>
+                        <td><p class="first">Takes one record and produces zero, one, or more records.  You can modify the record keys and values, including
+                            their types.
+                            (<a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KStream.html#flatMap-org.apache.kafka.streams.kstream.KeyValueMapper-">details</a>)</p>
+                            <p><strong>Marks the stream for data re-partitioning:</strong>
+                                Applying a grouping or a join after <code class="docutils literal"><span class="pre">flatMap</span></code> will result in re-partitioning of the records.
+                                If possible use <code class="docutils literal"><span class="pre">flatMapValues</span></code> instead, which will not cause data re-partitioning.</p>
+                            <div class="last highlight-java"><div class="highlight"><pre><span></span><span class="n">KStream</span><span class="o">&lt;</span><span class="n">Long</span><span class="o">,</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">stream</span> <span class="o">=</span> <span class="o">...;</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Integer</span><span class="o">&gt;</span> <span class="n">transformed</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">flatMap</span><span class="o">(</span>
+     <span class="c1">// Here, we generate two output records for each input record.</span>
+     <span class="c1">// We also change the key and value types.</span>
+     <span class="c1">// Example: (345L, &quot;Hello&quot;) -&gt; (&quot;HELLO&quot;, 1000), (&quot;hello&quot;, 9000)</span>
+    <span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="o">{</span>
+      <span class="n">List</span><span class="o">&lt;</span><span class="n">KeyValue</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Integer</span><span class="o">&gt;&gt;</span> <span class="n">result</span> <span class="o">=</span> <span class="k">new</span> <span class="n">LinkedList</span><span class="o">&lt;&gt;();</span>
+      <span class="n">result</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">KeyValue</span><span class="o">.</span><span class="na">pair</span><span class="o">(</span><span class="n">value</span><span class="o">.</span><span class="na">toUpperCase</span><span class="o">(),</span> <span class="mi">1000</span><span class="o">));</span>
+      <span class="n">result</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">KeyValue</span><span class="o">.</span><span class="na">pair</span><span class="o">(</span><span class="n">value</span><span class="o">.</span><span class="na">toLowerCase</span><span class="o">(),</span> <span class="mi">9000</span><span class="o">));</span>
+      <span class="k">return</span> <span class="n">result</span><span class="o">;</span>
+    <span class="o">}</span>
+  <span class="o">);</span>
+
+<span class="c1">// Java 7 example: cf. `map` for how to create `KeyValueMapper` instances</span>
+</pre></div>
+                            </div>
+                        </td>
+                    </tr>
+                    <tr class="row-even"><td><p class="first"><strong>FlatMap (values only)</strong></p>
+                        <ul class="last simple">
+                            <li>KStream &rarr; KStream</li>
+                        </ul>
+                    </td>
+                        <td><p class="first">Takes one record and produces zero, one, or more records, while retaining the key of the original record.
+                            You can modify the record values and the value type.
+                            (<a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KStream.html#flatMapValues-org.apache.kafka.streams.kstream.ValueMapper-">details</a>)</p>
+                            <p><code class="docutils literal"><span class="pre">flatMapValues</span></code> is preferable to <code class="docutils literal"><span class="pre">flatMap</span></code> because it will not cause data re-partitioning.  However, you
+                                cannot modify the key or key type like <code class="docutils literal"><span class="pre">flatMap</span></code> does.</p>
+                            <div class="last highlight-java"><div class="highlight"><pre><span></span><span class="c1">// Split a sentence into words.</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">sentences</span> <span class="o">=</span> <span class="o">...;</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">words</span> <span class="o">=</span> <span class="n">sentences</span><span class="o">.</span><span class="na">flatMapValues</span><span class="o">(</span><span class="n">value</span> <span class="o">-&gt;</span> <span class="n">Arrays</span><span class="o">.</span><span class="na">asList</span><span class [...]
+
+<span class="c1">// Java 7 example: cf. `mapValues` for how to create `ValueMapper` instances</span>
+</pre></div>
+                            </div>
+                        </td>
+                    </tr>
+                    <tr class="row-odd"><td><p class="first"><strong>Foreach</strong></p>
+                        <ul class="last simple">
+                            <li>KStream &rarr; void</li>
+                            <li>KStream &rarr; void</li>
+                            <li>KTable &rarr; void</li>
+                        </ul>
+                    </td>
+                        <td><p class="first"><strong>Terminal operation.</strong>  Performs a stateless action on each record.
+                            (<a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KStream.html#foreach-org.apache.kafka.streams.kstream.ForeachAction-">details</a>)</p>
+                            <p>You would use <code class="docutils literal"><span class="pre">foreach</span></code> to cause <em>side effects</em> based on the input data (similar to <code class="docutils literal"><span class="pre">peek</span></code>) and then <em>stop</em>
+                                <em>further processing</em> of the input data (unlike <code class="docutils literal"><span class="pre">peek</span></code>, which is not a terminal operation).</p>
+                            <p><strong>Note on processing guarantees:</strong> Any side effects of an action (such as writing to external systems) are not
+                                trackable by Kafka, which means they will typically not benefit from  Kafka&#8217;s processing guarantees.</p>
+                            <div class="last highlight-java"><div class="highlight"><pre><span></span><span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">stream</span> <span class="o">=</span> <span class="o">...;</span>
+
+<span class="c1">// Print the contents of the KStream to the local console.</span>
+<span class="c1">// Java 8+ example, using lambda expressions</span>
+<span class="n">stream</span><span class="o">.</span><span class="na">foreach</span><span class="o">((</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">key</span> <span class="o">+</span> <span class="s">&quot; =&gt; &quot;</sp [...]
+
+<span class="c1">// Java 7 example</span>
+<span class="n">stream</span><span class="o">.</span><span class="na">foreach</span><span class="o">(</span>
+    <span class="k">new</span> <span class="n">ForeachAction</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;()</span> <span class="o">{</span>
+      <span class="nd">@Override</span>
+      <span class="kd">public</span> <span class="kt">void</span> <span class="nf">apply</span><span class="o">(</span><span class="n">String</span> <span class="n">key</span><span class="o">,</span> <span class="n">Long</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
+        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">key</span> <span class="o">+</span> <span class="s">&quot; =&gt; &quot;</span> <span class="o">+</span> <span class="n">value</span><span class="o">);</span>
+      <span class="o">}</span>
+    <span class="o">});</span>
+</pre></div>
+                            </div>
+                        </td>
+                    </tr>
+                    <tr class="row-even"><td><p class="first"><strong>GroupByKey</strong></p>
+                        <ul class="last simple">
+                            <li>KStream &rarr; KGroupedStream</li>
+                        </ul>
+                    </td>
+                        <td><p class="first">Groups the records by the existing key.
+                            (<a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KStream.html#groupByKey--">details</a>)</p>
+                            <p>Grouping is a prerequisite for <a class="reference internal" href="#streams-developer-guide-dsl-aggregating"><span class="std std-ref">aggregating a stream or a table</span></a>
+                                and ensures that data is properly partitioned (&#8220;keyed&#8221;) for subsequent operations.</p>
+                            <p><strong>When to set explicit SerDes:</strong>
+                                Variants of <code class="docutils literal"><span class="pre">groupByKey</span></code> exist to override the configured default SerDes of your application, which <strong>you</strong>
+                                <strong>must do</strong> if the key and/or value types of the resulting <code class="docutils literal"><span class="pre">KGroupedStream</span></code> do not match the configured default
+                                SerDes.</p>
+                            <div class="admonition note">
+                                <p><b>Note</b></p>
+                                <p class="last"><strong>Grouping vs. Windowing:</strong>
+                                    A related operation is <a class="reference internal" href="#streams-developer-guide-dsl-windowing"><span class="std std-ref">windowing</span></a>, which lets you control how to
+                                    &#8220;sub-group&#8221; the grouped records <em>of the same key</em> into so-called <em>windows</em> for stateful operations such as
+                                    windowed <a class="reference internal" href="#streams-developer-guide-dsl-aggregating"><span class="std std-ref">aggregations</span></a> or
+                                    windowed <a class="reference internal" href="#streams-developer-guide-dsl-joins"><span class="std std-ref">joins</span></a>.</p>
+                            </div>
+                            <p><strong>Causes data re-partitioning if and only if the stream was marked for re-partitioning.</strong>
+                                <code class="docutils literal"><span class="pre">groupByKey</span></code> is preferable to <code class="docutils literal"><span class="pre">groupBy</span></code> because it re-partitions data only if the stream was already marked
+                                for re-partitioning. However, <code class="docutils literal"><span class="pre">groupByKey</span></code> does not allow you to modify the key or key type like <code class="docutils literal"><span class="pre">groupBy</span></code>
+                                does.</p>
+                            <div class="last highlight-java"><div class="highlight"><pre><span></span><span class="n">KStream</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">stream</span> <span class="o">=</span> <span class="o">...;</span>
+
+<span class="c1">// Group by the existing key, using the application&#39;s configured</span>
+<span class="c1">// default serdes for keys and values.</span>
+<span class="n">KGroupedStream</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">groupedStream</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">groupByKey</span><span class="o">();</span>
+
+<span class="c1">// When the key and/or value types do not match the configured</span>
+<span class="c1">// default serdes, we must explicitly specify serdes.</span>
+<span class="n">KGroupedStream</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">groupedStream</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">groupByKey</span><span class="o">(</span>
+    <span class="n">Grouped</span><span class="o">.</span><span class="na">with</span><span class="o">(</span>
+      <span class="n">Serdes</span><span class="o">.</span><span class="na">ByteArray</span><span class="o">(),</span> <span class="cm">/* key */</span>
+      <span class="n">Serdes</span><span class="o">.</span><span class="na">String</span><span class="o">())</span>     <span class="cm">/* value */</span>
+  <span class="o">);</span>
+</pre></div>
+                            </div>
+                        </td>
+                    </tr>
+                    <tr class="row-odd"><td><p class="first"><strong>GroupBy</strong></p>
+                        <ul class="last simple">
+                            <li>KStream &rarr; KGroupedStream</li>
+                            <li>KTable &rarr; KGroupedTable</li>
+                        </ul>
+                    </td>
+                        <td><p class="first">Groups the records by a <em>new</em> key, which may be of a different key type.
+                            When grouping a table, you may also specify a new value and value type.
+                            <code class="docutils literal"><span class="pre">groupBy</span></code> is a shorthand for <code class="docutils literal"><span class="pre">selectKey(...).groupByKey()</span></code>.
+                            (<a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KStream.html#groupBy-org.apache.kafka.streams.kstream.KeyValueMapper-">KStream details</a>,
+                            <a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KTable.html#groupBy-org.apache.kafka.streams.kstream.KeyValueMapper-">KTable details</a>)</p>
+                            <p>Grouping is a prerequisite for <a class="reference internal" href="#streams-developer-guide-dsl-aggregating"><span class="std std-ref">aggregating a stream or a table</span></a>
+                                and ensures that data is properly partitioned (&#8220;keyed&#8221;) for subsequent operations.</p>
+                            <p><strong>When to set explicit SerDes:</strong>
+                                Variants of <code class="docutils literal"><span class="pre">groupBy</span></code> exist to override the configured default SerDes of your application, which <strong>you must</strong>
+                                <strong>do</strong> if the key and/or value types of the resulting <code class="docutils literal"><span class="pre">KGroupedStream</span></code> or <code class="docutils literal"><span class="pre">KGroupedTable</span></code> do not match the
+                                configured default SerDes.</p>
+                            <div class="admonition note">
+                                <p><b>Note</b></p>
+                                <p class="last"><strong>Grouping vs. Windowing:</strong>
+                                    A related operation is <a class="reference internal" href="#streams-developer-guide-dsl-windowing"><span class="std std-ref">windowing</span></a>, which lets you control how to
+                                    &#8220;sub-group&#8221; the grouped records <em>of the same key</em> into so-called <em>windows</em> for stateful operations such as
+                                    windowed <a class="reference internal" href="#streams-developer-guide-dsl-aggregating"><span class="std std-ref">aggregations</span></a> or
+                                    windowed <a class="reference internal" href="#streams-developer-guide-dsl-joins"><span class="std std-ref">joins</span></a>.</p>
+                            </div>
+                            <p><strong>Always causes data re-partitioning:</strong>  <code class="docutils literal"><span class="pre">groupBy</span></code> always causes data re-partitioning.
+                                If possible use <code class="docutils literal"><span class="pre">groupByKey</span></code> instead, which will re-partition data only if required.</p>
+                            <div class="last highlight-java"><div class="highlight"><pre><span></span><span class="n">KStream</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">stream</span> <span class="o">=</span> <span class="o">...;</span>
+<span class="n">KTable</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">table</span> <span class="o">=</span> <span class="o">...;</span>
+
+<span class="c1">// Java 8+ examples, using lambda expressions</span>
+
+<span class="c1">// Group the stream by a new key and key type</span>
+<span class="n">KGroupedStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">groupedStream</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">groupBy</span><span class="o">(</span>
+    <span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">value</span><span class="o">,</span>
+    <span class="n">Grouped</span><span class="o">.</span><span class="na">with</span><span class="o">(</span>
+      <span class="n">Serdes</span><span class="o">.</span><span class="na">String</span><span class="o">(),</span> <span class="cm">/* key (note: type was modified) */</span>
+      <span class="n">Serdes</span><span class="o">.</span><span class="na">String</span><span class="o">())</span>  <span class="cm">/* value */</span>
+  <span class="o">);</span>
+
+<span class="c1">// Group the table by a new key and key type, and also modify the value and value type.</span>
+<span class="n">KGroupedTable</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Integer</span><span class="o">&gt;</span> <span class="n">groupedTable</span> <span class="o">=</span> <span class="n">table</span><span class="o">.</span><span class="na">groupBy</span><span class="o">(</span>
+    <span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">KeyValue</span><span class="o">.</span><span class="na">pair</span><span class="o">(</span><span class="n">value</span><span class="o">,</span> <span class="n">value</span><span class="o">.</span><span class="na">length</span><span class="o">()),</span>
+    <span class="n">Grouped</span><span class="o">.</span><span class="na">with</span><span class="o">(</span>
+      <span class="n">Serdes</span><span class="o">.</span><span class="na">String</span><span class="o">(),</span> <span class="cm">/* key (note: type was modified) */</span>
+      <span class="n">Serdes</span><span class="o">.</span><span class="na">Integer</span><span class="o">())</span> <span class="cm">/* value (note: type was modified) */</span>
+  <span class="o">);</span>
+
+
+<span class="c1">// Java 7 examples</span>
+
+<span class="c1">// Group the stream by a new key and key type</span>
+<span class="n">KGroupedStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">groupedStream</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">groupBy</span><span class="o">(</span>
+    <span class="k">new</span> <span class="n">KeyValueMapper</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">&gt;&gt;()</span> <span class="o">{</span>
+      <span class="nd">@Override</span>
+      <span class="kd">public</span> <span class="n">String</span> <span class="nf">apply</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">key</span><span class="o">,</span> <span class="n">String</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
+        <span class="k">return</span> <span class="n">value</span><span class="o">;</span>
+      <span class="o">}</span>
+    <span class="o">},</span>
+    <span class="n">Grouped</span><span class="o">.</span><span class="na">with</span><span class="o">(</span>
+      <span class="n">Serdes</span><span class="o">.</span><span class="na">String</span><span class="o">(),</span> <span class="cm">/* key (note: type was modified) */</span>
+      <span class="n">Serdes</span><span class="o">.</span><span class="na">String</span><span class="o">())</span>  <span class="cm">/* value */</span>
+  <span class="o">);</span>
+
+<span class="c1">// Group the table by a new key and key type, and also modify the value and value type.</span>
+<span class="n">KGroupedTable</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Integer</span><span class="o">&gt;</span> <span class="n">groupedTable</span> <span class="o">=</span> <span class="n">table</span><span class="o">.</span><span class="na">groupBy</span><span class="o">(</span>
+    <span class="k">new</span> <span class="n">KeyValueMapper</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">,</span> <span class="n">KeyValue</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Integer</span><span class="o">&gt;&gt;()</span> <span class="o">{</span>
+      <span class="nd">@Override</span>
+      <span class="kd">public</span> <span class="n">KeyValue</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Integer</span><span class="o">&gt;</span> <span class="nf">apply</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">key</span><span class="o">,</span> <span class="n">String</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
+        <span class="k">return</span> <span class="n">KeyValue</span><span class="o">.</span><span class="na">pair</span><span class="o">(</span><span class="n">value</span><span class="o">,</span> <span class="n">value</span><span class="o">.</span><span class="na">length</span><span class="o">());</span>
+      <span class="o">}</span>
+    <span class="o">},</span>
+    <span class="n">Grouped</span><span class="o">.</span><span class="na">with</span><span class="o">(</span>
+      <span class="n">Serdes</span><span class="o">.</span><span class="na">String</span><span class="o">(),</span> <span class="cm">/* key (note: type was modified) */</span>
+      <span class="n">Serdes</span><span class="o">.</span><span class="na">Integer</span><span class="o">())</span> <span class="cm">/* value (note: type was modified) */</span>
+  <span class="o">);</span>
+</pre></div>
+                            </div>
+                        </td>
+                    </tr>
+                    <tr class="row-even"><td><p class="first"><strong>Map</strong></p>
+                        <ul class="last simple">
+                            <li>KStream &rarr; KStream</li>
+                        </ul>
+                    </td>
+                        <td><p class="first">Takes one record and produces one record.  You can modify the record key and value, including their types.
+                            (<a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KStream.html#map-org.apache.kafka.streams.kstream.KeyValueMapper-">details</a>)</p>
+                            <p><strong>Marks the stream for data re-partitioning:</strong>
+                                Applying a grouping or a join after <code class="docutils literal"><span class="pre">map</span></code> will result in re-partitioning of the records.
+                                If possible use <code class="docutils literal"><span class="pre">mapValues</span></code> instead, which will not cause data re-partitioning.</p>
+                            <div class="last highlight-java"><div class="highlight"><pre><span></span><span class="n">KStream</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">stream</span> <span class="o">=</span> <span class="o">...;</span>
+
+<span class="c1">// Java 8+ example, using lambda expressions</span>
+<span class="c1">// Note how we change the key and the key type (similar to `selectKey`)</span>
+<span class="c1">// as well as the value and the value type.</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Integer</span><span class="o">&gt;</span> <span class="n">transformed</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">map</span><span class="o">(</span>
+    <span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">KeyValue</span><span class="o">.</span><span class="na">pair</span><span class="o">(</span><span class="n">value</span><span class="o">.</span><span class="na">toLowerCase</span><span class="o">(),</span> <span class="n">value</span><span class="o">.</span><span class="na">length</span><span class="o">()));</span>
+
+<span class="c1">// Java 7 example</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Integer</span><span class="o">&gt;</span> <span class="n">transformed</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">map</span><span class="o">(</span>
+    <span class="k">new</span> <span class="n">KeyValueMapper</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">,</span> <span class="n">KeyValue</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Integer</span><span class="o">&gt;&gt;()</span> <span class="o">{</span>
+      <span class="nd">@Override</span>
+      <span class="kd">public</span> <span class="n">KeyValue</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Integer</span><span class="o">&gt;</span> <span class="nf">apply</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">key</span><span class="o">,</span> <span class="n">String</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
+        <span class="k">return</span> <span class="k">new</span> <span class="n">KeyValue</span><span class="o">&lt;&gt;(</span><span class="n">value</span><span class="o">.</span><span class="na">toLowerCase</span><span class="o">(),</span> <span class="n">value</span><span class="o">.</span><span class="na">length</span><span class="o">());</span>
+      <span class="o">}</span>
+    <span class="o">});</span>
+</pre></div>
+                            </div>
+                        </td>
+                    </tr>
+                    <tr class="row-odd"><td><p class="first"><strong>Map (values only)</strong></p>
+                        <ul class="last simple">
+                            <li>KStream &rarr; KStream</li>
+                            <li>KTable &rarr; KTable</li>
+                        </ul>
+                    </td>
+                        <td><p class="first">Takes one record and produces one record, while retaining the key of the original record.
+                            You can modify the record value and the value type.
+                            (<a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KStream.html#mapValues-org.apache.kafka.streams.kstream.ValueMapper-">KStream details</a>,
+                            <a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KTable.html#mapValues-org.apache.kafka.streams.kstream.ValueMapper-">KTable details</a>)</p>
+                            <p><code class="docutils literal"><span class="pre">mapValues</span></code> is preferable to <code class="docutils literal"><span class="pre">map</span></code> because it will not cause data re-partitioning.  However, it does not
+                                allow you to modify the key or key type like <code class="docutils literal"><span class="pre">map</span></code> does.</p>
+                            <div class="last highlight-java"><div class="highlight"><pre><span></span><span class="n">KStream</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">stream</span> <span class="o">=</span> <span class="o">...;</span>
+
+<span class="c1">// Java 8+ example, using lambda expressions</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">uppercased</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">mapValues</span><span class="o">(</span><span class="n">value</span> <span class="o">-&gt;</span> <span class="n">value</span><span class="o">.</span><span class="na">toUpperCase</span><span cla [...]
+
+<span class="c1">// Java 7 example</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">uppercased</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">mapValues</span><span class="o">(</span>
+    <span class="k">new</span> <span class="n">ValueMapper</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;()</span> <span class="o">{</span>
+      <span class="nd">@Override</span>
+      <span class="kd">public</span> <span class="n">String</span> <span class="nf">apply</span><span class="o">(</span><span class="n">String</span> <span class="n">s</span><span class="o">)</span> <span class="o">{</span>
+        <span class="k">return</span> <span class="n">s</span><span class="o">.</span><span class="na">toUpperCase</span><span class="o">();</span>
+      <span class="o">}</span>
+    <span class="o">});</span>
+</pre></div>
+                            </div>
+                        </td>
+                    </tr>
+                    <tr class="row-even"><td><p class="first"><strong>Merge</strong></p>
+                        <ul class="last simple">
+                            <li>KStream &rarr; KStream</li>
+                        </ul>
+                    </td>
+                        <td><p class="first">Merges records of two streams into one larger stream.
+                            (<a class="reference external"
+                            href="../../../javadoc/org/apache/kafka/streams/kstream/KStream.html#merge-org.apache.kafka.streams.kstream.KStream-">details</a>)
+                            <p>There is no ordering guarantee between records
+                            from different streams in the merged stream. Relative order is preserved within each input stream though (ie, records within the same input stream are processed in order)</p>
+                            <div class="last highlight-java">
+                              <div class="highlight">
+                                <pre>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">stream1</span> <span class="o">=</span> <span class="o">...;</span>
+
+<span class="n">KStream</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">stream2</span> <span class="o">=</span> <span class="o">...;</span>
+
+<span class="n">KStream</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">merged</span> <span class="o">=</span> <span class="n">stream1</span><span class="o">.</span><span class="na">merge</span><span class="o">(</span><span class="n">stream2</span><span class="o">);</span>
+                                </pre>
+                              </div>
+                            </div>
+                        </td>
+                    </tr>
+                    <tr class="row-odd"><td><p class="first"><strong>Peek</strong></p>
+                        <ul class="last simple">
+                            <li>KStream &rarr; KStream</li>
+                        </ul>
+                    </td>
+                        <td><p class="first">Performs a stateless action on each record, and returns an unchanged stream.
+                            (<a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KStream.html#peek-org.apache.kafka.streams.kstream.ForeachAction-">details</a>)</p>
+                            <p>You would use <code class="docutils literal"><span class="pre">peek</span></code> to cause <em>side effects</em> based on the input data (similar to <code class="docutils literal"><span class="pre">foreach</span></code>) and <em>continue</em>
+                                <em>processing</em> the input data (unlike <code class="docutils literal"><span class="pre">foreach</span></code>, which is a terminal operation).  <code class="docutils literal"><span class="pre">peek</span></code> returns the input
+                                stream as-is;  if you need to modify the input stream, use <code class="docutils literal"><span class="pre">map</span></code> or <code class="docutils literal"><span class="pre">mapValues</span></code> instead.</p>
+                            <p><code class="docutils literal"><span class="pre">peek</span></code> is helpful for use cases such as logging or tracking metrics or for debugging and troubleshooting.</p>
+                            <p><strong>Note on processing guarantees:</strong> Any side effects of an action (such as writing to external systems) are not
+                                trackable by Kafka, which means they will typically not benefit from Kafka&#8217;s processing guarantees.</p>
+                            <div class="last highlight-java"><div class="highlight"><pre><span></span><span class="n">KStream</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">stream</span> <span class="o">=</span> <span class="o">...;</span>
+
+<span class="c1">// Java 8+ example, using lambda expressions</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">unmodifiedStream</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">peek</span><span class="o">(</span>
+    <span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&quot;key=&quot;</span> <span class="o">+</span> <span class="n">key</span> <span class="o">+</span> <span class="s">&quot;, value=&quot;</span> <span class [...]
+
+<span class="c1">// Java 7 example</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">unmodifiedStream</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">peek</span><span class="o">(</span>
+    <span class="k">new</span> <span class="n">ForeachAction</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;()</span> <span class="o">{</span>
+      <span class="nd">@Override</span>
+      <span class="kd">public</span> <span class="kt">void</span> <span class="nf">apply</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">key</span><span class="o">,</span> <span class="n">String</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
+        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&quot;key=&quot;</span> <span class="o">+</span> <span class="n">key</span> <span class="o">+</span> <span class="s">&quot;, value=&quot;</span> <span class="o">+</span> <span class="n">value</span><span class="o">);</span>
+      <span class="o">}</span>
+    <span class="o">});</span>
+</pre></div>
+                            </div>
+                        </td>
+                    </tr>
+                    <tr class="row-even"><td><p class="first"><strong>Print</strong></p>
+                        <ul class="last simple">
+                            <li>KStream &rarr; void</li>
+                        </ul>
+                    </td>
+                        <td><p class="first"><strong>Terminal operation.</strong>  Prints the records to <code class="docutils literal"><span class="pre">System.out</span></code>.  See Javadocs for serde and <code class="docutils literal"><span class="pre">toString()</span></code>
+                            caveats.
+                            (<a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KStream.html#print--">details</a>)</p>
+                            <p>Calling <code class="docutils literal"><span class="pre">print()</span></code> is the same as calling <code class="docutils literal"><span class="pre">foreach((key,</span> <span class="pre">value)</span> <span class="pre">-&gt;</span> <span class="pre">System.out.println(key</span> <span class="pre">+</span> <span class="pre">&quot;,</span> <span class="pre">&quot;</span> <span class="pre">+</span> <span class="pre">value))</span></code></p>
+                            <p><code class="docutils literal"><span class="pre">print</span></code> is mainly for debugging/testing purposes, and it will try to flush on each record print. Hence it <strong>should not</strong> be used for production usage if performance requirements are concerned.</p>
+                            <div class="last highlight-java"><div class="highlight"><pre><span></span><span class="n">KStream</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">stream</span> <span class="o">=</span> <span class="o">...;</span>
+<span class="c1">// print to sysout</span>
+<span class="n">stream</span><span class="o">.</span><span class="na">print</span><span class="o">();</span>
+
+<span class="c1">// print to file with a custom label</span>
+<span class="n">stream</span><span class="o">.</span><span class="na">print</span><span class="o">(</span><span class="n">Printed</span><span class="o">.</span><span class="na">toFile</span><span class="o">(</span><span class="s">&quot;streams.out&quot;</span><span class="o">).</span><span class="na">withLabel</span><span class="o">(</span><span class="s">&quot;streams&quot;</span><span class="o">));</span>
+</pre></div>
+                            </div>
+                        </td>
+                    </tr>
+                    <tr class="row-odd"><td><p class="first"><strong>SelectKey</strong></p>
+                        <ul class="last simple">
+                            <li>KStream &rarr; KStream</li>
+                        </ul>
+                    </td>
+                        <td><p class="first">Assigns a new key &#8211; possibly of a new key type &#8211; to each record.
+                            (<a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KStream.html#selectKey-org.apache.kafka.streams.kstream.KeyValueMapper-">details</a>)</p>
+                            <p>Calling <code class="docutils literal"><span class="pre">selectKey(mapper)</span></code> is the same as calling <code class="docutils literal"><span class="pre">map((key,</span> <span class="pre">value)</span> <span class="pre">-&gt;</span> <span class="pre">mapper(key,</span> <span class="pre">value),</span> <span class="pre">value)</span></code>.</p>
+                            <p><strong>Marks the stream for data re-partitioning:</strong>
+                                Applying a grouping or a join after <code class="docutils literal"><span class="pre">selectKey</span></code> will result in re-partitioning of the records.</p>
+                            <div class="last highlight-java"><div class="highlight"><pre><span></span><span class="n">KStream</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">stream</span> <span class="o">=</span> <span class="o">...;</span>
+
+<span class="c1">// Derive a new record key from the record&#39;s value.  Note how the key type changes, too.</span>
+<span class="c1">// Java 8+ example, using lambda expressions</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">rekeyed</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">selectKey</span><span class="o">((</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">value</s [...]
+
+<span class="c1">// Java 7 example</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">rekeyed</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">selectKey</span><span class="o">(</span>
+    <span class="k">new</span> <span class="n">KeyValueMapper</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">&gt;()</span> <span class="o">{</span>
+      <span class="nd">@Override</span>
+      <span class="kd">public</span> <span class="n">String</span> <span class="nf">apply</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">key</span><span class="o">,</span> <span class="n">String</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
+        <span class="k">return</span> <span class="n">value</span><span class="o">.</span><span class="na">split</span><span class="o">(</span><span class="s">&quot; &quot;</span><span class="o">)[</span><span class="mi">0</span><span class="o">];</span>
+      <span class="o">}</span>
+    <span class="o">});</span>
+</pre></div>
+                            </div>
+                        </td>
+                    </tr>
+                    <tr class="row-even"><td><p class="first"><strong>Table to Stream</strong></p>
+                        <ul class="last simple">
+                            <li>KTable &rarr; KStream</li>
+                        </ul>
+                    </td>
+                        <td><p class="first">Get the changelog stream of this table.
+                            (<a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KTable.html#toStream--">details</a>)</p>
+                            <div class="last highlight-java"><div class="highlight"><pre><span></span><span class="n">KTable</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">table</span> <span class="o">=</span> <span class="o">...;</span>
+
+<span class="c1">// Also, a variant of `toStream` exists that allows you</span>
+<span class="c1">// to select a new key for the resulting stream.</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">stream</span> <span class="o">=</span> <span class="n">table</span><span class="o">.</span><span class="na">toStream</span><span class="o">();</span>
+</pre></div>
+                            </div>
+                        </td>
+                    </tr>
+                    </tbody>
+                </table>
+            </div>
+            <div class="section" id="stateful-transformations">
+                <span id="streams-developer-guide-dsl-transformations-stateful"></span><h3><a class="toc-backref" href="#id11">Stateful transformations</a><a class="headerlink" href="#stateful-transformations" title="Permalink to this headline"></a></h3>
+                <p id="streams-developer-guide-dsl-transformations-stateful-overview">Stateful transformations depend on state for processing inputs and producing outputs and require a <a class="reference internal" href="../architecture.html#streams_architecture_state"><span class="std std-ref">state store</span></a> associated with the stream processor. For example, in aggregating operations, a windowing state store is used to collect the latest aggregation results per
+                    window. In join operations, a windowing state store is used to collect all of the records received so far within the
+                    defined window boundary.</p>
+                <p>Note, that state stores are fault-tolerant.
+                    In case of failure, Kafka Streams guarantees to fully restore all state stores prior to resuming the processing.
+                    See <a class="reference internal" href="../architecture.html#streams_architecture_recovery"><span class="std std-ref">Fault Tolerance</span></a> for further information.</p>
+                <p>Available stateful transformations in the DSL include:</p>
+                <ul class="simple">
+                    <li><a class="reference internal" href="#streams-developer-guide-dsl-aggregating"><span class="std std-ref">Aggregating</span></a></li>
+                    <li><a class="reference internal" href="#streams-developer-guide-dsl-joins"><span class="std std-ref">Joining</span></a></li>
+                    <li><a class="reference internal" href="#streams-developer-guide-dsl-windowing"><span class="std std-ref">Windowing</span></a> (as part of aggregations and joins)</li>
+                    <li><a class="reference internal" href="#streams-developer-guide-dsl-process"><span class="std std-ref">Applying custom processors and transformers</span></a>, which may be stateful, for
+                        Processor API integration</li>
+                </ul>
+                <p>The following diagram shows their relationships:</p>
+                <div class="figure align-center" id="id2">
+                    <img class="centered" src="/{{version}}/images/streams-stateful_operations.png">
+                    <p class="caption"><span class="caption-text">Stateful transformations in the DSL.</span></p>
+                </div>
+                <p>Here is an example of a stateful application: the WordCount algorithm.</p>
+                <p>WordCount example in Java 8+, using lambda expressions:</p>
+                <div class="highlight-java"><div class="highlight"><pre><span></span><span class="c1">// Assume the record values represent lines of text.  For the sake of this example, you can ignore</span>
+<span class="c1">// whatever may be stored in the record keys.</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">textLines</span> <span class="o">=</span> <span class="o">...;</span>
+
+<span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">wordCounts</span> <span class="o">=</span> <span class="n">textLines</span>
+    <span class="c1">// Split each text line, by whitespace, into words.  The text lines are the record</span>
+    <span class="c1">// values, i.e. you can ignore whatever data is in the record keys and thus invoke</span>
+    <span class="c1">// `flatMapValues` instead of the more generic `flatMap`.</span>
+    <span class="o">.</span><span class="na">flatMapValues</span><span class="o">(</span><span class="n">value</span> <span class="o">-&gt;</span> <span class="n">Arrays</span><span class="o">.</span><span class="na">asList</span><span class="o">(</span><span class="n">value</span><span class="o">.</span><span class="na">toLowerCase</span><span class="o">().</span><span class="na">split</span><span class="o">(</span><span class="s">&quot;\\W+&quot;</span><span class="o">)))</span>
+    <span class="c1">// Group the stream by word to ensure the key of the record is the word.</span>
+    <span class="o">.</span><span class="na">groupBy</span><span class="o">((</span><span class="n">key</span><span class="o">,</span> <span class="n">word</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">word</span><span class="o">)</span>
+    <span class="c1">// Count the occurrences of each word (record key).</span>
+    <span class="c1">//</span>
+    <span class="c1">// This will change the stream type from `KGroupedStream&lt;String, String&gt;` to</span>
+    <span class="c1">// `KTable&lt;String, Long&gt;` (word -&gt; count).</span>
+    <span class="o">.</span><span class="na">count</span><span class="o">()</span>
+    <span class="c1">// Convert the `KTable&lt;String, Long&gt;` into a `KStream&lt;String, Long&gt;`.</span>
+    <span class="o">.</span><span class="na">toStream</span><span class="o">();</span>
+</pre></div>
+                </div>
+                <p>WordCount example in Java 7:</p>
+                <div class="highlight-java"><div class="highlight"><pre><span></span><span class="c1">// Code below is equivalent to the previous Java 8+ example above.</span>
+<span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">textLines</span> <span class="o">=</span> <span class="o">...;</span>
+
+<span class="n">KStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">wordCounts</span> <span class="o">=</span> <span class="n">textLines</span>
+    <span class="o">.</span><span class="na">flatMapValues</span><span class="o">(</span><span class="k">new</span> <span class="n">ValueMapper</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Iterable</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;&gt;()</span> <span class="o">{</span>
+        <span class="nd">@Override</span>
+        <span class="kd">public</span> <span class="n">Iterable</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="nf">apply</span><span class="o">(</span><span class="n">String</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
+            <span class="k">return</span> <span class="n">Arrays</span><span class="o">.</span><span class="na">asList</span><span class="o">(</span><span class="n">value</span><span class="o">.</span><span class="na">toLowerCase</span><span class="o">().</span><span class="na">split</span><span class="o">(</span><span class="s">&quot;\\W+&quot;</span><span class="o">));</span>
+        <span class="o">}</span>
+    <span class="o">})</span>
+    <span class="o">.</span><span class="na">groupBy</span><span class="o">(</span><span class="k">new</span> <span class="n">KeyValueMapper</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">&gt;&gt;()</span> <span class="o">{</span>
+        <span class="nd">@Override</span>
+        <span class="kd">public</span> <span class="n">String</span> <span class="nf">apply</span><span class="o">(</span><span class="n">String</span> <span class="n">key</span><span class="o">,</span> <span class="n">String</span> <span class="n">word</span><span class="o">)</span> <span class="o">{</span>
+            <span class="k">return</span> <span class="n">word</span><span class="o">;</span>
+        <span class="o">}</span>
+    <span class="o">})</span>
+    <span class="o">.</span><span class="na">count</span><span class="o">()</span>
+    <span class="o">.</span><span class="na">toStream</span><span class="o">();</span>
+</pre></div>
+                </div>
+                <div class="section" id="aggregating">
+                    <span id="streams-developer-guide-dsl-aggregating"></span><h4><a class="toc-backref" href="#id12">Aggregating</a><a class="headerlink" href="#aggregating" title="Permalink to this headline"></a></h4>
+                    <p>After records are <a class="reference internal" href="#streams-developer-guide-dsl-transformations-stateless"><span class="std std-ref">grouped</span></a> by key via <code class="docutils literal"><span class="pre">groupByKey</span></code> or
+                        <code class="docutils literal"><span class="pre">groupBy</span></code> &#8211; and thus represented as either a <code class="docutils literal"><span class="pre">KGroupedStream</span></code> or a <code class="docutils literal"><span class="pre">KGroupedTable</span></code>, they can be aggregated
+                        via an operation such as <code class="docutils literal"><span class="pre">reduce</span></code>.  Aggregations are key-based operations, which means that they always operate over records
+                        (notably record values) of the same key.
+                        You can perform aggregations on <a class="reference internal" href="#streams-developer-guide-dsl-windowing"><span class="std std-ref">windowed</span></a> or non-windowed data.</p>
+                    <table border="1" class="non-scrolling-table width-100-percent docutils">
+                        <colgroup>
+                            <col width="22%" />
+                            <col width="78%" />
+                        </colgroup>
+                        <thead valign="bottom">
+                        <tr class="row-odd"><th class="head">Transformation</th>
+                            <th class="head">Description</th>
+                        </tr>
+                        </thead>
+                        <tbody valign="top">
+                        <tr class="row-even"><td><p class="first"><strong>Aggregate</strong></p>
+                            <ul class="last simple">
+                                <li>KGroupedStream &rarr; KTable</li>
+                                <li>KGroupedTable &rarr; KTable</li>
+                            </ul>
+                        </td>
+                            <td><p class="first"><strong>Rolling aggregation.</strong> Aggregates the values of (non-windowed) records by the grouped key.
+                                Aggregating is a generalization of <code class="docutils literal"><span class="pre">reduce</span></code> and allows, for example, the aggregate value to have a different
+                                type than the input values.
+                                (<a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KGroupedStream.html">KGroupedStream details</a>,
+                                <a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KGroupedTable.html">KGroupedTable details</a>)</p>
+                                <p>When aggregating a <em>grouped stream</em>, you must provide an initializer (e.g., <code class="docutils literal"><span class="pre">aggValue</span> <span class="pre">=</span> <span class="pre">0</span></code>) and an &#8220;adder&#8221;
+                                    aggregator (e.g., <code class="docutils literal"><span class="pre">aggValue</span> <span class="pre">+</span> <span class="pre">curValue</span></code>).  When aggregating a <em>grouped table</em>, you must provide a
+                                    &#8220;subtractor&#8221; aggregator (think: <code class="docutils literal"><span class="pre">aggValue</span> <span class="pre">-</span> <span class="pre">oldValue</span></code>).</p>
+                                <p>Several variants of <code class="docutils literal"><span class="pre">aggregate</span></code> exist, see Javadocs for details.</p>
+                                <div class="highlight-java"><div class="highlight"><pre><span></span><span class="n">KGroupedStream</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">groupedStream</span> <span class="o">=</span> <span class="o">...;</span>
+<span class="n">KGroupedTable</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">groupedTable</span> <span class="o">=</span> <span class="o">...;</span>
+
+<span class="c1">// Java 8+ examples, using lambda expressions</span>
+
+<span class="c1">// Aggregating a KGroupedStream (note how the value type changes from String to Long)</span>
+<span class="n">KTable</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">aggregatedStream</span> <span class="o">=</span> <span class="n">groupedStream</span><span class="o">.</span><span class="na">aggregate</span><span class="o">(</span>
+    <span class="o">()</span> <span class="o">-&gt;</span> <span class="mi">0</span><span class="n">L</span><span class="o">,</span> <span class="cm">/* initializer */</span>
+    <span class="o">(</span><span class="n">aggKey</span><span class="o">,</span> <span class="n">newValue</span><span class="o">,</span> <span class="n">aggValue</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">aggValue</span> <span class="o">+</span> <span class="n">newValue</span><span class="o">.</span><span class="na">length</span><span class="o">(),</span> <span class="cm">/* adder */</span>
+    <span class="n">Materialized</span><span class="o">.</span><span class="na">as</span><span class="o">(</span><span class="s">&quot;aggregated-stream-store&quot;</span><span class="o">)</span> <span class="cm">/* state store name */</span>
+        <span class="o">.</span><span class="na">withValueSerde</span><span class="o">(</span><span class="n">Serdes</span><span class="o">.</span><span class="na">Long</span><span class="o">());</span> <span class="cm">/* serde for aggregate value */</span>
+
+<span class="c1">// Aggregating a KGroupedTable (note how the value type changes from String to Long)</span>
+<span class="n">KTable</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">aggregatedTable</span> <span class="o">=</span> <span class="n">groupedTable</span><span class="o">.</span><span class="na">aggregate</span><span class="o">(</span>
+    <span class="o">()</span> <span class="o">-&gt;</span> <span class="mi">0</span><span class="n">L</span><span class="o">,</span> <span class="cm">/* initializer */</span>
+    <span class="o">(</span><span class="n">aggKey</span><span class="o">,</span> <span class="n">newValue</span><span class="o">,</span> <span class="n">aggValue</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">aggValue</span> <span class="o">+</span> <span class="n">newValue</span><span class="o">.</span><span class="na">length</span><span class="o">(),</span> <span class="cm">/* adder */</span>
+    <span class="o">(</span><span class="n">aggKey</span><span class="o">,</span> <span class="n">oldValue</span><span class="o">,</span> <span class="n">aggValue</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">aggValue</span> <span class="o">-</span> <span class="n">oldValue</span><span class="o">.</span><span class="na">length</span><span class="o">(),</span> <span class="cm">/* subtractor */</span>
+    <span class="n">Materialized</span><span class="o">.</span><span class="na">as</span><span class="o">(</span><span class="s">&quot;aggregated-table-store&quot;</span><span class="o">)</span> <span class="cm">/* state store name */</span>
+	<span class="o">.</span><span class="na">withValueSerde</span><span class="o">(</span><span class="n">Serdes</span><span class="o">.</span><span class="na">Long</span><span class="o">())</span> <span class="cm">/* serde for aggregate value */</span>
+
+
+<span class="c1">// Java 7 examples</span>
+
+<span class="c1">// Aggregating a KGroupedStream (note how the value type changes from String to Long)</span>
+<span class="n">KTable</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">aggregatedStream</span> <span class="o">=</span> <span class="n">groupedStream</span><span class="o">.</span><span class="na">aggregate</span><span class="o">(</span>
+    <span class="k">new</span> <span class="n">Initializer</span><span class="o">&lt;</span><span class="n">Long</span><span class="o">&gt;()</span> <span class="o">{</span> <span class="cm">/* initializer */</span>
+      <span class="nd">@Override</span>
+      <span class="kd">public</span> <span class="n">Long</span> <span class="nf">apply</span><span class="o">()</span> <span class="o">{</span>
+        <span class="k">return</span> <span class="mi">0</span><span class="n">L</span><span class="o">;</span>
+      <span class="o">}</span>
+    <span class="o">},</span>
+    <span class="k">new</span> <span class="n">Aggregator</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;()</span> <span class="o">{</span> <span class="cm">/* adder */</span>
+      <span class="nd">@Override</span>
+      <span class="kd">public</span> <span class="n">Long</span> <span class="nf">apply</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">aggKey</span><span class="o">,</span> <span class="n">String</span> <span class="n">newValue</span><span class="o">,</span> <span class="n">Long</span> <span class="n">aggValue</span><span class="o">)</span> <span class="o">{</span>
+        <span class="k">return</span> <span class="n">aggValue</span> <span class="o">+</span> <span class="n">newValue</span><span class="o">.</span><span class="na">length</span><span class="o">();</span>
+      <span class="o">}</span>
+    <span class="o">},</span>
+    <span class="n">Materialized</span><span class="o">.</span><span class="na">as</span><span class="o">(</span><span class="s">&quot;aggregated-stream-store&quot;</span><span class="o">)</span>
+        <span class="o">.</span><span class="na">withValueSerde</span><span class="o">(</span><span class="n">Serdes</span><span class="o">.</span><span class="na">Long</span><span class="o">());</span>
+
+<span class="c1">// Aggregating a KGroupedTable (note how the value type changes from String to Long)</span>
+<span class="n">KTable</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">aggregatedTable</span> <span class="o">=</span> <span class="n">groupedTable</span><span class="o">.</span><span class="na">aggregate</span><span class="o">(</span>
+    <span class="k">new</span> <span class="n">Initializer</span><span class="o">&lt;</span><span class="n">Long</span><span class="o">&gt;()</span> <span class="o">{</span> <span class="cm">/* initializer */</span>
+      <span class="nd">@Override</span>
+      <span class="kd">public</span> <span class="n">Long</span> <span class="nf">apply</span><span class="o">()</span> <span class="o">{</span>
+        <span class="k">return</span> <span class="mi">0</span><span class="n">L</span><span class="o">;</span>
+      <span class="o">}</span>
+    <span class="o">},</span>
+    <span class="k">new</span> <span class="n">Aggregator</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;()</span> <span class="o">{</span> <span class="cm">/* adder */</span>
+      <span class="nd">@Override</span>
+      <span class="kd">public</span> <span class="n">Long</span> <span class="nf">apply</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">aggKey</span><span class="o">,</span> <span class="n">String</span> <span class="n">newValue</span><span class="o">,</span> <span class="n">Long</span> <span class="n">aggValue</span><span class="o">)</span> <span class="o">{</span>
+        <span class="k">return</span> <span class="n">aggValue</span> <span class="o">+</span> <span class="n">newValue</span><span class="o">.</span><span class="na">length</span><span class="o">();</span>
+      <span class="o">}</span>
+    <span class="o">},</span>
+    <span class="k">new</span> <span class="n">Aggregator</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[],</span> <span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;()</span> <span class="o">{</span> <span class="cm">/* subtractor */</span>
+      <span class="nd">@Override</span>
+      <span class="kd">public</span> <span class="n">Long</span> <span class="nf">apply</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">aggKey</span><span class="o">,</span> <span class="n">String</span> <span class="n">oldValue</span><span class="o">,</span> <span class="n">Long</span> <span class="n">aggValue</span><span class="o">)</span> <span class="o">{</span>
+        <span class="k">return</span> <span class="n">aggValue</span> <span class="o">-</span> <span class="n">oldValue</span><span class="o">.</span><span class="na">length</span><span class="o">();</span>
+      <span class="o">}</span>
+    <span class="o">},</span>
+    <span class="n">Materialized</span><span class="o">.</span><span class="na">as</span><span class="o">(</span><span class="s">&quot;aggregated-stream-store&quot;</span><span class="o">)</span>
+        <span class="o">.</span><span class="na">withValueSerde</span><span class="o">(</span><span class="n">Serdes</span><span class="o">.</span><span class="na">Long</span><span class="o">());</span>
+</pre></div>
+                                </div>
+                                <p>Detailed behavior of <code class="docutils literal"><span class="pre">KGroupedStream</span></code>:</p>
+                                <ul class="simple">
+                                    <li>Input records with <code class="docutils literal"><span class="pre">null</span></code> keys are ignored.</li>
+                                    <li>When a record key is received for the first time, the initializer is called (and called before the adder).</li>
+                                    <li>Whenever a record with a non-<code class="docutils literal"><span class="pre">null</span></code> value is received, the adder is called.</li>
+                                </ul>
+                                <p>Detailed behavior of <code class="docutils literal"><span class="pre">KGroupedTable</span></code>:</p>
+                                <ul class="simple">
+                                    <li>Input records with <code class="docutils literal"><span class="pre">null</span></code> keys are ignored.</li>
+                                    <li>When a record key is received for the first time, the initializer is called (and called before the adder
+                                        and subtractor).  Note that, in contrast to <code class="docutils literal"><span class="pre">KGroupedStream</span></code>, over time the initializer may be called
+                                        more than once for a key as a result of having received input tombstone records for that key (see below).</li>
+                                    <li>When the first non-<code class="docutils literal"><span class="pre">null</span></code> value is received for a key (e.g.,  INSERT), then only the adder is called.</li>
+                                    <li>When subsequent non-<code class="docutils literal"><span class="pre">null</span></code> values are received for a key (e.g.,  UPDATE), then (1) the subtractor is
+                                        called with the old value as stored in the table and (2) the adder is called with the new value of the
+                                        input record that was just received.  The order of execution for the subtractor and adder is not defined.</li>
+                                    <li>When a tombstone record &#8211; i.e. a record with a <code class="docutils literal"><span class="pre">null</span></code> value &#8211; is received for a key (e.g.,  DELETE),
+                                        then only the subtractor is called.  Note that, whenever the subtractor returns a <code class="docutils literal"><span class="pre">null</span></code> value itself,
+                                        then the corresponding key is removed from the resulting <code class="docutils literal"><span class="pre">KTable</span></code>.  If that happens, any next input
+                                        record for that key will trigger the initializer again.</li>
+                                </ul>
+                                <p class="last">See the example at the bottom of this section for a visualization of the aggregation semantics.</p>
+                            </td>
+                        </tr>
+                        <tr class="row-odd"><td><p class="first"><strong>Aggregate (windowed)</strong></p>
+                            <ul class="last simple">
+                                <li>KGroupedStream &rarr; KTable</li>
+                            </ul>
+                        </td>
+                            <td><p class="first"><strong>Windowed aggregation.</strong>
+                                Aggregates the values of records, <a class="reference internal" href="#streams-developer-guide-dsl-windowing"><span class="std std-ref">per window</span></a>, by the grouped key.
+                                Aggregating is a generalization of <code class="docutils literal"><span class="pre">reduce</span></code> and allows, for example, the aggregate value to have a different
+                                type than the input values.
+                                (<a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/TimeWindowedKStream.html">TimeWindowedKStream details</a>,
+                                <a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/SessionWindowedKStream.html">SessionWindowedKStream details</a>)</p>
+                                <p>You must provide an initializer (e.g.,  <code class="docutils literal"><span class="pre">aggValue</span> <span class="pre">=</span> <span class="pre">0</span></code>), &#8220;adder&#8221; aggregator (e.g.,  <code class="docutils literal"><span class="pre">aggValue</span> <span class="pre">+</span> <span class="pre">curValue</span></code>),
+                                    and a window.  When windowing based on sessions, you must additionally provide a &#8220;session merger&#8221; aggregator
+                                    (e.g.,  <code class="docutils literal"><span class="pre">mergedAggValue</span> <span class="pre">=</span> <span class="pre">leftAggValue</span> <span class="pre">+</span> <span class="pre">rightAggValue</span></code>).</p>
+                                <p>The windowed <code class="docutils literal"><span class="pre">aggregate</span></code> turns a <code class="docutils literal"><span class="pre">TimeWindowedKStream&lt;K,</span> <span class="pre">V&gt;</span></code> or <code class="docutils literal"><span class="pre">SessionWindowdKStream&lt;K,</span> <span class="pre">V&gt;</span></code>
+                                    into a windowed <code class="docutils literal"><span class="pre">KTable&lt;Windowed&lt;K&gt;,</span> <span class="pre">V&gt;</span></code>.</p>
+                                <p>Several variants of <code class="docutils literal"><span class="pre">aggregate</span></code> exist, see Javadocs for details.</p>
+                                <div class="highlight-java"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">java.time.Duration</span><span class="o">;</span>
+<span class="n">KGroupedStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">groupedStream</span> <span class="o">=</span> <span class="o">...;</span>
+
+<span class="c1">// Java 8+ examples, using lambda expressions</span>
+
+<span class="c1">// Aggregating with time-based windowing (here: with 5-minute tumbling windows)</span>
+<span class="n">KTable</span><span class="o">&lt;</span><span class="n">Windowed</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">timeWindowedAggregatedStream</span> <span class="o">=</span> <span class="n">groupedStream</span><span class="o">.</span><span class="na">windowedBy</span><span class="o">(</span><span class="n">Duration</span><span class="o">.</span><span class="na [...]
+    <span class="o">.</span><span class="na">aggregate</span><span class="o">(</span>
+        <span class="o">()</span> <span class="o">-&gt;</span> <span class="mi">0</span><span class="n">L</span><span class="o">,</span> <span class="cm">/* initializer */</span>
+        <span class="o">(</span><span class="n">aggKey</span><span class="o">,</span> <span class="n">newValue</span><span class="o">,</span> <span class="n">aggValue</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">aggValue</span> <span class="o">+</span> <span class="n">newValue</span><span class="o">,</span> <span class="cm">/* adder */</span>
+        <span class="n">Materialized</span><span class="o">.&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">,</span> <span class="n">WindowStore</span><span class="o">&lt;</span><span class="n">Bytes</span><span class="o">,</span> <span class="kt">byte</span><span class="o">[]&gt;&gt;</span><span class="n">as</span><span class="o">(</span><span class="s">&quot;time-windowed-aggregated-stream-store&quot;</span><span class="o">)</ [...]
+        <span class="o">.</span><span class="na">withValueSerde</span><span class="o">(</span><span class="n">Serdes</span><span class="o">.</span><span class="na">Long</span><span class="o">()));</span> <span class="cm">/* serde for aggregate value */</span>
+
+<span class="c1">// Aggregating with session-based windowing (here: with an inactivity gap of 5 minutes)</span>
+<span class="n">KTable</span><span class="o">&lt;</span><span class="n">Windowed</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">sessionizedAggregatedStream</span> <span class="o">=</span> <span class="n">groupedStream</span><span class="o">.</span><span class="na">windowedBy</span><span class="o">(</span><span class="n">SessionWindows</span><span class="o">.</span><span clas [...]
+    <span class="n">aggregate</span><span class="o">(</span>
+    	<span class="o">()</span> <span class="o">-&gt;</span> <span class="mi">0</span><span class="n">L</span><span class="o">,</span> <span class="cm">/* initializer */</span>
+    	<span class="o">(</span><span class="n">aggKey</span><span class="o">,</span> <span class="n">newValue</span><span class="o">,</span> <span class="n">aggValue</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">aggValue</span> <span class="o">+</span> <span class="n">newValue</span><span class="o">,</span> <span class="cm">/* adder */</span>
+        <span class="o">(</span><span class="n">aggKey</span><span class="o">,</span> <span class="n">leftAggValue</span><span class="o">,</span> <span class="n">rightAggValue</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">leftAggValue</span> <span class="o">+</span> <span class="n">rightAggValue</span><span class="o">,</span> <span class="cm">/* session merger */</span>
+        <span class="n">Materialized</span><span class="o">.&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">,</span> <span class="n">SessionStore</span><span class="o">&lt;</span><span class="n">Bytes</span><span class="o">,</span> <span class="kt">byte</span><span class="o">[]&gt;&gt;</span><span class="n">as</span><span class="o">(</span><span class="s">&quot;sessionized-aggregated-stream-store&quot;</span><span class="o">)</s [...]
+        <span class="o">.</span><span class="na">withValueSerde</span><span class="o">(</span><span class="n">Serdes</span><span class="o">.</span><span class="na">Long</span><span class="o">()));</span> <span class="cm">/* serde for aggregate value */</span>
+
+<span class="c1">// Java 7 examples</span>
+
+<span class="c1">// Aggregating with time-based windowing (here: with 5-minute tumbling windows)</span>
+<span class="n">KTable</span><span class="o">&lt;</span><span class="n">Windowed</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">timeWindowedAggregatedStream</span> <span class="o">=</span> <span class="n">groupedStream</span><span class="o">.</span><span class="na">windowedBy</span><span class="o">(</span><span class="n">Duration</span><span class="o">.</span><span class="na [...]
+    <span class="o">.</span><span class="na">aggregate</span><span class="o">(</span>
+        <span class="k">new</span> <span class="n">Initializer</span><span class="o">&lt;</span><span class="n">Long</span><span class="o">&gt;()</span> <span class="o">{</span> <span class="cm">/* initializer */</span>
+            <span class="nd">@Override</span>
+            <span class="kd">public</span> <span class="n">Long</span> <span class="nf">apply</span><span class="o">()</span> <span class="o">{</span>
+                <span class="k">return</span> <span class="mi">0</span><span class="n">L</span><span class="o">;</span>
+            <span class="o">}</span>
+        <span class="o">},</span>
+        <span class="k">new</span> <span class="n">Aggregator</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;()</span> <span class="o">{</span> <span class="cm">/* adder */</span>
+            <span class="nd">@Override</span>
+            <span class="kd">public</span> <span class="n">Long</span> <span class="nf">apply</span><span class="o">(</span><span class="n">String</span> <span class="n">aggKey</span><span class="o">,</span> <span class="n">Long</span> <span class="n">newValue</span><span class="o">,</span> <span class="n">Long</span> <span class="n">aggValue</span><span class="o">)</span> <span class="o">{</span>
+                <span class="k">return</span> <span class="n">aggValue</span> <span class="o">+</span> <span class="n">newValue</span><span class="o">;</span>
+            <span class="o">}</span>
+        <span class="o">},</span>
+        <span class="n">Materialized</span><span class="o">.&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">,</span> <span class="n">WindowStore</span><span class="o">&lt;</span><span class="n">Bytes</span><span class="o">,</span> <span class="kt">byte</span><span class="o">[]&gt;&gt;</span><span class="n">as</span><span class="o">(</span><span class="s">&quot;time-windowed-aggregated-stream-store&quot;</span><span class="o">)</span>
+          <span class="o">.</span><span class="na">withValueSerde</span><span class="o">(</span><span class="n">Serdes</span><span class="o">.</span><span class="na">Long</span><span class="o">()));</span>
+
+<span class="c1">// Aggregating with session-based windowing (here: with an inactivity gap of 5 minutes)</span>
+<span class="n">KTable</span><span class="o">&lt;</span><span class="n">Windowed</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">sessionizedAggregatedStream</span> <span class="o">=</span> <span class="n">groupedStream</span><span class="o">.</span><span class="na">windowedBy</span><span class="o">(</span><span class="n">SessionWindows</span><span class="o">.</span><span clas [...]
+    <span class="n">aggregate</span><span class="o">(</span>
+        <span class="k">new</span> <span class="n">Initializer</span><span class="o">&lt;</span><span class="n">Long</span><span class="o">&gt;()</span> <span class="o">{</span> <span class="cm">/* initializer */</span>
+            <span class="nd">@Override</span>
+            <span class="kd">public</span> <span class="n">Long</span> <span class="nf">apply</span><span class="o">()</span> <span class="o">{</span>
+                <span class="k">return</span> <span class="mi">0</span><span class="n">L</span><span class="o">;</span>
+            <span class="o">}</span>
+        <span class="o">},</span>
+        <span class="k">new</span> <span class="n">Aggregator</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;()</span> <span class="o">{</span> <span class="cm">/* adder */</span>
+            <span class="nd">@Override</span>
+            <span class="kd">public</span> <span class="n">Long</span> <span class="nf">apply</span><span class="o">(</span><span class="n">String</span> <span class="n">aggKey</span><span class="o">,</span> <span class="n">Long</span> <span class="n">newValue</span><span class="o">,</span> <span class="n">Long</span> <span class="n">aggValue</span><span class="o">)</span> <span class="o">{</span>
+                <span class="k">return</span> <span class="n">aggValue</span> <span class="o">+</span> <span class="n">newValue</span><span class="o">;</span>
+            <span class="o">}</span>
+        <span class="o">},</span>
+        <span class="k">new</span> <span class="n">Merger</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;()</span> <span class="o">{</span> <span class="cm">/* session merger */</span>
+            <span class="nd">@Override</span>
+            <span class="kd">public</span> <span class="n">Long</span> <span class="nf">apply</span><span class="o">(</span><span class="n">String</span> <span class="n">aggKey</span><span class="o">,</span> <span class="n">Long</span> <span class="n">leftAggValue</span><span class="o">,</span> <span class="n">Long</span> <span class="n">rightAggValue</span><span class="o">)</span> <span class="o">{</span>
+                <span class="k">return</span> <span class="n">rightAggValue</span> <span class="o">+</span> <span class="n">leftAggValue</span><span class="o">;</span>
+            <span class="o">}</span>
+        <span class="o">},</span>
+        <span class="n">Materialized</span><span class="o">.&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">,</span> <span class="n">SessionStore</span><span class="o">&lt;</span><span class="n">Bytes</span><span class="o">,</span> <span class="kt">byte</span><span class="o">[]&gt;&gt;</span><span class="n">as</span><span class="o">(</span><span class="s">&quot;sessionized-aggregated-stream-store&quot;</span><span class="o">)</span>
+          <span class="o">.</span><span class="na">withValueSerde</span><span class="o">(</span><span class="n">Serdes</span><span class="o">.</span><span class="na">Long</span><span class="o">()));</span>
+</pre></div>
+                                </div>
+                                <p>Detailed behavior:</p>
+                                <ul class="simple">
+                                    <li>The windowed aggregate behaves similar to the rolling aggregate described above.  The additional twist is that
+                                        the behavior applies <em>per window</em>.</li>
+                                    <li>Input records with <code class="docutils literal"><span class="pre">null</span></code> keys are ignored in general.</li>
+                                    <li>When a record key is received for the first time for a given window, the initializer is called (and called
+                                        before the adder).</li>
+                                    <li>Whenever a record with a non-<code class="docutils literal"><span class="pre">null</span></code> value is received for a given window, the adder is called.</li>
+                                    <li>When using session windows: the session merger is called whenever two sessions are being merged.</li>
+                                </ul>
+                                <p class="last">See the example at the bottom of this section for a visualization of the aggregation semantics.</p>
+                            </td>
+                        </tr>
+                        <tr class="row-even"><td><p class="first"><strong>Count</strong></p>
+                            <ul class="last simple">
+                                <li>KGroupedStream &rarr; KTable</li>
+                                <li>KGroupedTable &rarr; KTable</li>
+                            </ul>
+                        </td>
+                            <td><p class="first"><strong>Rolling aggregation.</strong> Counts the number of records by the grouped key.
+                                (<a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KGroupedStream.html">KGroupedStream details</a>,
+                                <a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KGroupedTable.html">KGroupedTable details</a>)</p>
+                                <p>Several variants of <code class="docutils literal"><span class="pre">count</span></code> exist, see Javadocs for details.</p>
+                                <div class="highlight-java"><div class="highlight"><pre><span></span><span class="n">KGroupedStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">groupedStream</span> <span class="o">=</span> <span class="o">...;</span>
+<span class="n">KGroupedTable</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">groupedTable</span> <span class="o">=</span> <span class="o">...;</span>
+
+<span class="c1">// Counting a KGroupedStream</span>
+<span class="n">KTable</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">aggregatedStream</span> <span class="o">=</span> <span class="n">groupedStream</span><span class="o">.</span><span class="na">count</span><span class="o">();</span>
+
+<span class="c1">// Counting a KGroupedTable</span>
+<span class="n">KTable</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">aggregatedTable</span> <span class="o">=</span> <span class="n">groupedTable</span><span class="o">.</span><span class="na">count</span><span class="o">();</span>
+</pre></div>
+                                </div>
+                                <p>Detailed behavior for <code class="docutils literal"><span class="pre">KGroupedStream</span></code>:</p>
+                                <ul class="simple">
+                                    <li>Input records with <code class="docutils literal"><span class="pre">null</span></code> keys or values are ignored.</li>
+                                </ul>
+                                <p>Detailed behavior for <code class="docutils literal"><span class="pre">KGroupedTable</span></code>:</p>
+                                <ul class="last simple">
+                                    <li>Input records with <code class="docutils literal"><span class="pre">null</span></code> keys are ignored.  Records with <code class="docutils literal"><span class="pre">null</span></code> values are not ignored but interpreted
+                                        as &#8220;tombstones&#8221; for the corresponding key, which indicate the deletion of the key from the table.</li>
+                                </ul>
+                            </td>
+                        </tr>
+                        <tr class="row-odd"><td><p class="first"><strong>Count (windowed)</strong></p>
+                            <ul class="last simple">
+                                <li>KGroupedStream &rarr; KTable</li>
+                            </ul>
+                        </td>
+                            <td><p class="first"><strong>Windowed aggregation.</strong>
+                                Counts the number of records, <a class="reference internal" href="#streams-developer-guide-dsl-windowing"><span class="std std-ref">per window</span></a>, by the grouped key.
+                                (<a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/TimeWindowedKStream.html">TimeWindowedKStream details</a>,
+                                <a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/SessionWindowedKStream.html">SessionWindowedKStream details</a>)</p>
+                                <p>The windowed <code class="docutils literal"><span class="pre">count</span></code> turns a <code class="docutils literal"><span class="pre">TimeWindowedKStream&lt;K,</span> <span class="pre">V&gt;</span></code> or <code class="docutils literal"><span class="pre">SessionWindowedKStream&lt;K,</span> <span class="pre">V&gt;</span></code>
+                                    into a windowed <code class="docutils literal"><span class="pre">KTable&lt;Windowed&lt;K&gt;,</span> <span class="pre">V&gt;</span></code>.</p>
+                                <p>Several variants of <code class="docutils literal"><span class="pre">count</span></code> exist, see Javadocs for details.</p>
+                                <div class="highlight-java"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">java.time.Duration</span><span class="o">;</span>
+<span class="n">KGroupedStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">groupedStream</span> <span class="o">=</span> <span class="o">...;</span>
+
+<span class="c1">// Counting a KGroupedStream with time-based windowing (here: with 5-minute tumbling windows)</span>
+<span class="n">KTable</span><span class="o">&lt;</span><span class="n">Windowed</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">aggregatedStream</span> <span class="o">=</span> <span class="n">groupedStream</span><span class="o">.</span><span class="na">windowedBy</span><span class="o">(</span>
+    <span class="n">TimeWindows</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="n">Duration</span><span class="o">.</span><span class="na">ofMinutes</span><span class="o">(</span><span class="mi">5</span><span class="o">)))</span> <span class="cm">/* time-based window */</span>
+    <span class="o">.</span><span class="na">count</span><span class="o">();</span>
+
+<span class="c1">// Counting a KGroupedStream with session-based windowing (here: with 5-minute inactivity gaps)</span>
+<span class="n">KTable</span><span class="o">&lt;</span><span class="n">Windowed</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">aggregatedStream</span> <span class="o">=</span> <span class="n">groupedStream</span><span class="o">.</span><span class="na">windowedBy</span><span class="o">(</span>
+    <span class="n">SessionWindows</span><span class="o">.</span><span class="na">with</span><span class="o">(</span><span class="n">Duration</span><span class="o">.</span><span class="na">ofMinutes</span><span class="o">(</span><span class="mi">5</span><span class="o">)))</span> <span class="cm">/* session window */</span>
+    <span class="o">.</span><span class="na">count</span><span class="o">();</span>
+</pre></div>
+                                </div>
+                                <p>Detailed behavior:</p>
+                                <ul class="last simple">
+                                    <li>Input records with <code class="docutils literal"><span class="pre">null</span></code> keys or values are ignored.</li>
+                                </ul>
+                            </td>
+                        </tr>
+                        <tr class="row-even"><td><p class="first"><strong>Reduce</strong></p>
+                            <ul class="last simple">
+                                <li>KGroupedStream &rarr; KTable</li>
+                                <li>KGroupedTable &rarr; KTable</li>
+                            </ul>
+                        </td>
+                            <td><p class="first"><strong>Rolling aggregation.</strong> Combines the values of (non-windowed) records by the grouped key.
+                                The current record value is combined with the last reduced value, and a new reduced value is returned.
+                                The result value type cannot be changed, unlike <code class="docutils literal"><span class="pre">aggregate</span></code>.
+                                (<a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KGroupedStream.html">KGroupedStream details</a>,
+                                <a class="reference external" href="../../../javadoc/org/apache/kafka/streams/kstream/KGroupedTable.html">KGroupedTable details</a>)</p>
+                                <p>When reducing a <em>grouped stream</em>, you must provide an &#8220;adder&#8221; reducer (e.g.,  <code class="docutils literal"><span class="pre">aggValue</span> <span class="pre">+</span> <span class="pre">curValue</span></code>).
+                                    When reducing a <em>grouped table</em>, you must additionally provide a &#8220;subtractor&#8221; reducer (e.g.,
+                                    <code class="docutils literal"><span class="pre">aggValue</span> <span class="pre">-</span> <span class="pre">oldValue</span></code>).</p>
+                                <p>Several variants of <code class="docutils literal"><span class="pre">reduce</span></code> exist, see Javadocs for details.</p>
+                                <div class="highlight-java"><div class="highlight"><pre><span></span><span class="n">KGroupedStream</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">groupedStream</span> <span class="o">=</span> <span class="o">...;</span>
+<span class="n">KGroupedTable</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">groupedTable</span> <span class="o">=</span> <span class="o">...;</span>
+
+<span class="c1">// Java 8+ examples, using lambda expressions</span>
+
+<span class="c1">// Reducing a KGroupedStream</span>
+<span class="n">KTable</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">aggregatedStream</span> <span class="o">=</span> <span class="n">groupedStream</span><span class="o">.</span><span class="na">reduce</span><span class="o">(</span>
+    <span class="o">(</span><span class="n">aggValue</span><span class="o">,</span> <span class="n">newValue</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">aggValue</span> <span class="o">+</span> <span class="n">newValue</span> <span class="cm">/* adder */</span><span class="o">);</span>
+
+<span class="c1">// Reducing a KGroupedTable</span>
+<span class="n">KTable</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">aggregatedTable</span> <span class="o">=</span> <span class="n">groupedTable</span><span class="o">.</span><span class="na">reduce</span><span class="o">(</span>
+    <span class="o">(</span><span class="n">aggValue</span><span class="o">,</span> <span class="n">newValue</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">aggValue</span> <span class="o">+</span> <span class="n">newValue</span><span class="o">,</span> <span class="cm">/* adder */</span>
+    <span class="o">(</span><span class="n">aggValue</span><span class="o">,</span> <span class="n">oldValue</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">aggValue</span> <span class="o">-</span> <span class="n">oldValue</span> <span class="cm">/* subtractor */</span><span class="o">);</span>
+
+
+<span class="c1">// Java 7 examples</span>
+
+<span class="c1">// Reducing a KGroupedStream</span>
+<span class="n">KTable</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">aggregatedStream</span> <span class="o">=</span> <span class="n">groupedStream</span><span class="o">.</span><span class="na">reduce</span><span class="o">(</span>
+    <span class="k">new</span> <span class="n">Reducer</span><span class="o">&lt;</span><span class="n">Long</span><span class="o">&gt;()</span> <span class="o">{</span> <span class="cm">/* adder */</span>
+      <span class="nd">@Override</span>
+      <span class="kd">public</span> <span class="n">Long</span> <span class="nf">apply</span><span class="o">(</span><span class="n">Long</span> <span class="n">aggValue</span><span class="o">,</span> <span class="n">Long</span> <span class="n">newValue</span><span class="o">)</span> <span class="o">{</span>
+        <span class="k">return</span> <span class="n">aggValue</span> <span class="o">+</span> <span class="n">newValue</span><span class="o">;</span>
+      <span class="o">}</span>
+    <span class="o">});</span>
+
+<span class="c1">// Reducing a KGroupedTable</span>
+<span class="n">KTable</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">Long</span><span class="o">&gt;</span> <span class="n">aggregatedTable</span> <span class="o">=</span> <span class="n">groupedTable</span><span class="o">.</span><span class="na">reduce</span><span class="o">(</span>
+    <span class="k">new</span> <span class="n">Reducer</span><span class="o">&lt;</span><span class="n">Long</span><span class="o">&gt;()</span> <span class="o">{</span> <span class="cm">/* adder */</span>
+      <span class="nd">@Override</span>
... 8452 lines suppressed ...