Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Details of deprecation of listConsumerGroups

Table of Contents

Status

Current stateUnder discussionAccepted

Discussion thread: here

JIRA: https://issues.apache.org/jira/browse/KAFKA-16891

...

Here’s an example of the complexity. If you start up a distributed Kafka Connect worker using the default configuration, it creates a group called "connect-cluster" . This is a group, but it’s not a consumer group. You can’t see this group in the list of consumer groups with the kafka-consumer-groups.sh  tool, but if you try to describe a consumer group called "connect-cluster"  or even use this group ID with a consumer, you get an error.

Here's another example. In Apache Kafka 3.8, if If you use kafka-consumer-groups.sh --describe --group MYSHARE  where the group is a share group, the output is Error: Consumer group 'MYSHARE' does not exist . That's probably OK, but it's interesting to see how it gets there.

First, the admin client uses the ConsumerGroupDescribe RPC which responds with error code GROUP_ID_NOT_FOUND (69)  and an empty error messagegiving no indication that it found a group of the wrong type. Next, the admin client falls back to the pre-KIP-848 DescribeGroups RPC in case it's a classic consumer group.  This RPC succeeds and responds with error code NONE (0)  and returns returning the group description with a status of Dead . It looks like a dead consumer group. Finally, this dead group is translated into the error message Error: Consumer group 'MYSHARE' does not exist . The output seems kind of acceptable, but the tool actually thinks it's dealing with a dead consumer group. It would be better if the ConsumerGroupDescribe RPC failed in a straightforward way.

...

This KIP introduces a command-line tool called kafka-groups.sh  for displaying all of the groups and their types.

In situations where command-line tools are used to administer a group of the wrong type, you’ll now be told the error message now indicates when the group type is wrong, rather than saying the group does not exist. This is achieved using a new error message in the Kafka protocol and a slight change in behavior of the admin client.

Listing groups

The KIP introduces a new tool called kafka-groups.sh to show all of the groups in a cluster, their types and the protocols they use. This lets you see consumer groups, share groups, Kafka Connect cluster groups, and any other custom groups all together. It doesn't replace the specific tools for the different types of group, but it does shed light on what's actually going on for administrators. Note that this does not require any changes to the Kafka protocol. The information is already available, but not directly accessible by the administrator.

...

Describing groups using the admin client

The behavior of AdminClienterror message of the exception GroupIdNotFoundException  is used by Admin.describeConsumerGroups(Collection<String>) seems a little unusual. You can describe a collection of group IDs, some of which might exist and others might not. Here's how the response is built:

  1. If the group is a consumer group and the client is authorized to describe the group and there was no error, the group information is returned, along with the authorized operations if requested.
  2. If the group is not a consumer group (either does not exist or wrong type) and the client is authorized to describe the group and there was no error, the group information for a dead group is returned, along with the authorized operations if requested.
  3. If the client is not authorized to the describe the group, the group information error code is set to GROUP_AUTHORIZATION_FAILED . 
  4. If there was an error describing the group, the group information error code is set.

In cases (1) and (2), the admin client considers the operation a success, and this means the KafkaFuture for this group completes successfully. In cases (3) and (4), the admin client considers the operation unsuccessful, and this means the KafkaFuture for this group completes exceptionally. Case (2) is the tricky one because you can't readily tell what the dead group means. This is why the admin tools use output like Error: Consumer group 'MYSHARE" does not exist, even when the group ID is recognised and it's just the wrong type.

This gives a problem though because even though it's not difficult to determine which groups have the incorrect type, it would take a breaking change to the admin client to discover this situation if using the admin client.

As a result, this KIP introduces a new option on DescribeConsumerGroupOptions  called validateGroupType  which changes the behavior in the case where a group ID is the wrong group type. For case (2) above, the new INCONSISTENT_GROUP_TYPE  error in the ConsumerGroupDescribe response is translated into an InconsistentGroupTypeException containing the error message from the RPC response, and that can be used by the admin tools.

Public Interfaces

Client API changes

AdminClient

Add the following methods on the org.apache.kafka.client.admin.AdminClient  interface.

...

and Admin.describeShareGroups(Collection<String>)  to indicate that the group being described has the wrong type. This small change enables the error messages from the command line kafka-consumer-groups.sh  and kafka-share-groups.sh  to indicate when the group ID is found, but it has the wrong type.

Unified group state

The Admin client interfaces to list groups provide the ability to filter by group state. By introducing a method for listing groups of all types, the question arises how to handle group state. Previously, there were separate enums for ConsumerGroupState  and ShareGroupState . Actually, ConsumerGroupState contains the set of states for both classic and modern consumer groups, which overlap but are not quite the same. ShareGroupState  is a subset of the states in ConsumerGroupState

This KIP introduces a single enum GroupState  which contains all of the states from existing enums. In practice, that means the states are exactly the same as those in ConsumerGroupState .

Then, ConsumerGroupState  is deprecated in favour of GroupState , and ShareGroupState  is immediately replaced by GroupState because the KIP-932 interfaces have not actually been released yet.

The methods which return ConsumerGroupState will be deprecated and replaced with methods called groupState()  which return GroupState  instead.

Public Interfaces

Client API changes

Admin

Add the following methods on the org.apache.kafka.client.admin.Admin  interface.

Method signatureDescription
DescribeClassicGroupsResult describeClassicGroups(Collection<String> groupIds) Describe some classic groups in the cluster, with the default options.
DescribeClassicGroupsResult describeClassicGroups(Collection<String> groupIds, DescribeClassicGroupsOptions options) Describe some classic groups in the cluster.
ListGroupsResult listGroups() List the groups available in the cluster.
ListGroupsResult listGroups(ListGroupsOptions options) List the groups available in the cluster.

It also deprecates the following methods because listGroups  with a type filter is preferred for listing groups rather than adding separate methods for each group type over time. The equivalent share groups methods will be removed from KIP-932.

Method signatureDescription
ListConsumerGroupsResult listConsumerGroups() List the consumer groups available in the cluster, with the default options.
ListConsumerGroupsResult listConsumerGroups(ListConsumerGroupsOptions options) List the consumer groups in the cluster.

Also deprecated are the related classes such as ListConsumerGroupsResult  and ListConsumerGroupsOptions .

Here are the method signatures of the new methodsHere are the method signatures:

Code Block
   /**
    * ListDescribe some theclassic groups available in the cluster with the default options.
    *
    * <p>This@param isgroupIds aThe convenienceIDs methodof forthe {@link #listGroups(ListGroupsOptions)} with default optionsgroups to describe.
    * See@param options the overloadThe foroptions moreto details.
use when describing the *groups.
    * @return The ListGroupsResultDescribeClassicGroupsResult.
    */
   defaultDescribeClassicGroupsResult ListGroupsResult listGroupsdescribeClassicGroups()Collection<String> {groupIds,
       return listGroups(new ListGroupsOptions());
   }
 
   /**
    * List the groups available in the cluster.
    *
    * @param options The options to use when listing the groups.
    * @return The ListGroupsResult.
    */
  DescribeClassicGroupsOptions ListGroupsResult listGroups(ListGroupsOptions options);

ListGroupOptions

Code Block
package org.apache.kafka.client.admin;

 
import org.apache.kafka.common.GroupType;
 
/**
 * Options for {@link Admin#listGroups(ListGroupsOptions)}.
 *
 *Describe Thesome APIclassic ofgroups thisin classthe is evolvingcluster, seewith {@linkthe Admin} for detailsdefault options.
 */
@InterfaceStability.Evolving
public class ListGroupsOptions extends* AbstractOptions<ListGroupsOptions> {<p>

    /**
     * If types is set, only groups of these types will be returned by listGroups()* This is a convenience method for {@link #describeClassicGroups(Collection, DescribeClassicGroupsOptions)}
    * with default options. See the overload for more details.
     * Otherwise, all
    * @param groupIds The IDs of the groups areto returneddescribe.
     */
 @return The DescribeClassicGroupsResult.
 public ListGroupsOptions withTypes(Set<GroupType> types) { */
   default DescribeClassicGroupsResult describeClassicGroups(Collection<String> groupIds) {
 this.types = (types == null || types.isEmpty()) ? Collections.emptySet() : new HashSet<>(types return describeClassicGroups(groupIds, new DescribeClassicGroupsOptions());
   }

   /**
   return this;
* List the groups }

available in the cluster /**
with the default options.
  * Returns the*
 list of group types* that<p>This areis requesteda orconvenience emptymethod iffor no types have been specified.
 {@link #listGroups(ListGroupsOptions)} with default options.
    */
 See the overload publicfor Set<GroupType> types() {more details.
    *
    return* types;
@return The   }
}

AbstractListGroupsResult

Code Block
package org.apache.kafka.clients.admin;
ListGroupsResult.
    
*/**
 * This classdefault implementsListGroupsResult the common APIs that are shared by results classes
 * for various AdminClient commands for listing groups.
 * <p>
 * The API of this class is evolving, see {@link Admin} for details.
 */
@InterfaceStability.Evolving
public class AbstractListGroupsResult<T extends GroupListing> {

    AbstractListGroupsResult(KafkaFuture<Collection<Object>> future);

    /**
     * Returns a future that yields either an exception, or the full set of group listings.
     */
    public KafkaFuture<Collection<T>> all() {
    }
  
    /**
     * Returns a future which yields just the valid listings.
     */listGroups() {
       return listGroups(new ListGroupsOptions());
   }
 
   /**
    * List the groups available in the cluster.
    *
    * @param options The options to use when listing the groups.
    * @return The ListGroupsResult.
    */
   ListGroupsResult listGroups(ListGroupsOptions options);

DescribeClassicGroupsOptions

Code Block
/**
 * Options for {@link Admin#describeClassicGroups(Collection, DescribeClassicGroupsOptions)}.
 * <p>
 * The API of this class is evolving, see {@link Admin} for details.
 */
@InterfaceStability.Evolving
public class DescribeClassicGroupsOptions extends AbstractOptions<DescribeClassicGroupsOptions> {
    private boolean includeAuthorizedOperations;

    public KafkaFuture<Collection<T>>DescribeClassicGroupsOptions validincludeAuthorizedOperations(boolean includeAuthorizedOperations) {
    }
   
 this.includeAuthorizedOperations   /**= includeAuthorizedOperations;
     * Returns a futurereturn whichthis;
 yields just the errors}

 which occurred.
  public boolean includeAuthorizedOperations() */{
    public   KafkaFuture<Collection<Throwable>> errors() {return includeAuthorizedOperations;
    }
}

...

DescribeClassicGroupsResult

Code Block
package org.apache.kafka.clientsclient.admin;
   
/**
 * The result of the {@link Admin#listGroups(ListGroupsOptions)KafkaAdminClient#describeClassicGroups(Collection, DescribeClassicGroupsOptions)}} call.
 * <p>
 * The API of this class is evolving, see {@link Admin} for details.
 */
@InterfaceStability.Evolving
public class ListGroupsResult extends AbstractListGroupsResult<GroupListing>DescribeClassicGroupsResult {

    public ListGroupsResult(KafkaFuture<Collection<Object>> future) {DescribeClassicGroupsResult(final Map<String, KafkaFuture<ClassicGroupDescription>> futures);

    /**
     * Return a map from group id to futures which yield group descriptions.
     */
   super(future public Map<String, KafkaFuture<ClassicGroupDescription>> describedGroups();

    }
}

ListConsumerGroupsResult

This is changed to extends AbstractListGroupsResult<ConsumerGroupListing> .

ListShareGroupsResult

This is changed to extend AbstractListGroupsResult<ShareGroupListing> .

GroupListing

/**
     * Return a future which yields all ClassicGroupDescription objects, if all the describes succeed.
     */
    public KafkaFuture<Map<String, ClassicGroupDescription>> all();
}

ClassicGroupDescription

Code Block
package org.apache.kafka.client.admin;

/**
 * A detailed description of a single classic group in the cluster
Code Block
package org.apache.kafka.client.admin;
  
import org.apache.kafka.common.ShareGroupState;
  
/**
 * A listing of a group in the cluster.
 * <p>
 * The API of this class is evolving, see {@link Admin} for details.
 */
@InterfaceStability.Evolving
public class GroupListingClassicGroupDescription {
    public GroupListingClassicGroupDescription(String groupId,
 String protocol);
  public GroupListing(String groupId,  GroupType type, String protocol);
  public GroupListing(String groupId, Optional<GroupType> type, String protocol);

  /**
   * The id of the group.
   */
  public String groupId();protocol,
  
  /**
   * The group type.
   */
  public Optional<GroupType> type();
  
  /**
   * The group protocol type.
   */
  public String protocol();
}

ConsumerGroupListing

This class is modified to extend org.apache.kafka.clients.admin.GroupListing .

ShareGroupListing

This class is modified to extend org.apache.kafka.clients.admin.GroupListing .

DescribeConsumerGroupsOptions

The following methods are added to this class.

Code Block
/**
 * Set to true if describing a group which is not a consumer group fails.
 */
public DescribeConsumerGroupsOptions validateGroupType(boolean validateGroupType);

/**
 * Set to true if describing a group which is not a consumer group fails.
 */
public boolean validateGroupType();

If validateGroupType  is set, when the ConsumerGroupDescribe RPC response contains the error code INCONSISTENT_GROUP_TYPE , the describe fails with InconsistentGroupTypeException . If it is not set, the error code is treated the same as GROUP_ID_NOT_FOUND  and the describe succeeds by returning a ConsumerGroupDescription  in Dead  state.

DescribeShareGroupsOptions

The following methods are added to this class.

Code Block
/**
 * Set to true if describing a group which is not a share group fails.
 */
public DescribeShareGroupsOptions validateGroupType(boolean validateGroupType);

/**
 * Set to true if describing a group which is not a share group fails.
 */
public boolean validateGroupType();

If validateGroupType  is set, when the ShareGroupDescribe RPC response contains the error code INCONSISTENT_GROUP_TYPE , the describe fails with InconsistentGroupTypeException . If it is not set, the error code is treated the same as GROUP_ID_NOT_FOUND  and the describe succeeds by returning a ShareGroupDescription  in Dead  state.

Exceptions

The following new exception is added to the org.apache.kafka.common.errors  package corresponding to the new error code in the Kafka protocol.

  • InconsistentGroupTypeException  - Indicates that the group exists but the group type is inconsistent with the operation.

Kafka protocol changes

This KIP adds the following error code to the Kafka protocol.

  • INCONSISTENT_GROUP_TYPE  (value TBD) - Indicates that the group exists but the group type is inconsistent with the operation.

This error code is used when the following RPCs are used with an existing group of the wrong type:

  • ConsumerGroupDescribe
  • ShareGroupDescribe

These RPCs are used by administrative tools and the new error code will help with the usability of the tools. Assuming that this KIP is delivered later than KIP-848, a new version of ConsumerGroupDescribe with no schema change will be required to support the new error code.

The remaining RPCs which work with groups, such as ListOffsets and TxnOffsetCommit, continue to fail with GROUP_ID_NOT_FOUND  if used against a group of the wrong type.

Command-line tools

kafka-groups.sh

A new tool called kafka-groups.sh  is introduced for listing and describing groups of any kind. It has the following options:

...

--bootstrap-server <String: server to connect to>

...

REQUIRED: The server(s) to connect to.

...

--command-config <String: command config property file>

...

Property file containing configs to be passed to Admin Client.

...

--consumer

...

Filters the groups based on group type and protocol in order to show consumer groups.

...

--describe

...

Describe the details of the groups.

...

--group-type <String: type>

...

Filters the groups based on group type. Valid types are: 'classic', 'consumer' and 'share'.

...

--help

...

Print usage information.

...

--list

...

List all groups.

...

--protocol <String: protocol>

...

Filters the groups based on protocol type.

...

--version

...

Display Kafka version.

 Collection<MemberDescription> members,
                                   String partitionAssignor,
                                   GroupState groupState,
                                   Node coordinator);

    public ClassicGroupDescription(String groupId,
                                   String protocol,
                                   Collection<MemberDescription> members,
                                   String partitionAssignor,
                                   GroupState groupState,
                                   Node coordinator,
                                   Set<AclOperation> authorizedOperations);

    /**
     * The id of the classic group.
     */
    public String groupId();

    /**
     * The group protocol type.
     */
    public String protocol();

    /**
     * If the group is a simple consumer group or not.
     */
    public boolean isSimpleConsumerGroup();

    /**
     * A list of the members of the classic group.
     */
    public Collection<MemberDescription> members();

    /**
     * The group partition assignor.
     */
    public String partitionAssignor();

    /**
     * The group state, or UNKNOWN if the state is too new for us to parse.
     */
    public GroupState groupState();

    /**
     * The group coordinator, or null if the coordinator is not known.
     */
    public Node coordinator();

    /**
     * authorizedOperations for this group, or null if that information is not known.
     */
    public Set<AclOperation> authorizedOperations();
}

ConsumerGroupDescription

The existing constructors are deprecated and equivalents which use GroupState  instead of ConsumerGroupState  are added.

The method ConsumerGroupState state()  is deprecated and GroupState groupState()  is added.

ListGroupsOptions

Code Block
package org.apache.kafka.client.admin;
 
import org.apache.kafka.common.GroupType;
 
/**
 * Options for {@link Admin#listGroups(ListGroupsOptions)}.
 *
 * The API of this class is evolving, see {@link Admin} for details.
 */
@InterfaceStability.Evolving
public class ListGroupsOptions extends AbstractOptions<ListGroupsOptions> {

    /**
     * Only consumer groups will be returned by listGroups().
     * This operation sets filters on group type and protocol type which select consumer groups.
     */
    public static ListGroupsOptions forConsumerGroups() {
        return new ListGroupsOptions()
            .withTypes(Set.of(GroupType.CLASSIC, GroupType.CONSUMER))
            .withProtocolTypes(Set.of("", ConsumerProtocol.PROTOCOL_TYPE));
    }

    /**
     * Only share groups will be returned by listGroups().
     * This operation sets a filter on group type which select share groups.
     */
    public static ListGroupsOptions forShareGroups() {
        return new ListGroupsOptions()
            .withTypes(Set.of(GroupType.SHARE));
    }

    /**
     * Only streams groups will be returned by listGroups().
     * This operation sets a filter on group type which select streams groups.
     */
    public static ListGroupsOptions forStreamsGroups() {
        return new ListGroupsOptions()
            .withTypes(Set.of(GroupType.STREAMS));
    }

    /**
     * If groupStates is set, only groups in these states will be returned by listGroups().
     * Otherwise, all groups are returned.
     * This operation is supported by brokers with version 2.6.0 or later.
     */
    public ListGroupsOptions inGroupStates(Set<GroupState> groupStates) {
        this.groupStates = (groupStates == null || groupStates.isEmpty()) ? Collections.emptySet() : new HashSet<>(groupStates);
        return this;
    }

    /**
     * If protocol types is set, only groups of these protocol types will be returned by listGroups().
     * Otherwise, all groups are returned.
     */
    public ListGroupsOptions withProtocolTypes(Set<String> protocolTypes) {
        this.protocolTypes = (protocolTypes == null || protocolTypes.isEmpty()) ? Set.of() : Set.copyOf(protocolTypes);
        return this;
    }

     /**
     * If types is set, only groups of these types will be returned by listGroups().
     * Otherwise, all groups are returned.
     */
    public ListGroupsOptions withTypes(Set<GroupType> types) {
        this.types = (types == null || types.isEmpty()) ? Collections.emptySet() : new HashSet<>(types);
        return this;
    }

    /**
     * Returns the list of group states that are requested or empty if not states have been specified.
     */
    public Set<GroupState> groupStates() {
        return groupStates;
    }

    /**
     * Returns the list of protocol types that are requested or empty if no protocol types have been specified.
     */
    public Set<String> protocolTypes() {
        return protocolTypes;
    }

    /**
     * Returns the list of group types that are requested or empty if no types have been specified.
     */
    public Set<GroupType> types() {
        return types;
    }
}

ListGroupsResult

Code Block
package org.apache.kafka.clients.admin;
   
/**
 * The result of the {@link Admin#listGroups(ListGroupsOptions)} call.
 * <p>
 * The API of this class is evolving, see {@link Admin} for details.
 */
@InterfaceStability.Evolving
public class ListGroupsResult {
    ListGroupsResult(KafkaFuture<Collection<Object>> future) {
        super(future);
    }

    /**
     * Returns a future that yields either an exception, or the full set of group listings.
     */
    public KafkaFuture<Collection<GroupListing>> all() {
    }
  
    /**
     * Returns a future which yields just the valid listings.
     */
    public KafkaFuture<Collection<GroupListing>> valid() {
    }
   
    /**
     * Returns a future which yields just the errors which occurred.
     */
    public KafkaFuture<Collection<Throwable>> errors() {
    }
}

GroupListing

Code Block
package org.apache.kafka.client.admin;
  
/**
 * A listing of a group in the cluster.
 * <p>
 * The API of this class is evolving, see {@link Admin} for details.
 */
@InterfaceStability.Evolving
public class GroupListing {
  public GroupListing(String groupId, Optional<GroupType> type, String protocol);

  /**
   * The id of the group.
   */
  public String groupId();
  
  /**
   * The group type.
   */
  public Optional<GroupType> type();
  
  /**
   * The group protocol type.
   */
  public String protocol();

  /**
   * The group state.
   */
  public Optional<GroupState> groupState();

  /**
   * If the group is a simple consumer group or not.
   */
  public boolean isSimpleConsumerGroup();
}

The new GroupState  enum contains the states for all types of groups. The following table shows the correspondence between the group states and types. This table will be included in the javadoc.

StateClassic groupClassic consumer groupModern consumer groupShare group
UNKNOWNYesYesYesYes
PREPARING_REBALANCEYesYes

COMPLETING_REBALANCEYesYes

STABLEYesYesYesYes
DEADYesYesYesYes
EMPTYYesYesYesYes
ASSIGNING

Yes
RECONCILING

Yes


Code Block
package org.apache.kafka.common;

/**
 * The group state.
 */
public enum GroupState {
    UNKNOWN("Unknown"),
    PREPARING_REBALANCE("PreparingRebalance"),
    COMPLETING_REBALANCE("CompletingRebalance"),
    STABLE("Stable"),
    DEAD("Dead"),
    EMPTY("Empty"),
    ASSIGNING("Assigning"),
    RECONCILING("Reconciling");

    GroupState(String name);

    /**
     * Case-insensitive group state lookup by string name.
     */
    public static GroupState parse(String name);

    public String toString();
}

Kafka protocol changes

This KIP introduces a new version for the DescribeGroups API.

DescribeGroups API

The KIP introduces version 6. This changes the error behavior so that if a classic group is described and the group ID either refers to a group of a different type, or the group ID is not found, the error code GROUP_ID_NOT_FOUND  is returned. Previously, the error code  NONE  was used with a group state of DEAD .

Request schema

Version 6 is the same as version 5.

Code Block
{
  "apiKey": 15,
  "type": "request",
  "listeners": ["zkBroker", "broker"],
  "name": "DescribeGroupsRequest",
  // Versions 1 and 2 are the same as version 0.
  //
  // Starting in version 3, authorized operations can be requested.
  //
  // Starting in version 4, the response will include group.instance.id info for members.
  //
  // Version 5 is the first flexible version.
  //
  // Version 6 returns error code GROUP_ID_NOT_FOUND if the group ID is not found (KIP-1043).
  "validVersions": "0-6",
  "flexibleVersions": "6+",
  "fields": [
    { "name": "Groups", "type": "[]string", "versions": "0+", "entityType": "groupId",
      "about": "The names of the groups to describe" },
    { "name": "IncludeAuthorizedOperations", "type": "bool", "versions": "3+",
      "about": "Whether to include authorized operations." }
  ]
}

Response schema

Version 6 adds the ErrorMessage  to the DescribedGroup  to enable the reasons for a non-existent group to be understood more clearly.

Code Block
{
  "apiKey": 15,
  "type": "response",
  "name": "DescribeGroupsResponse",
  // Version 1 added throttle time.
  //
  // Starting in version 2, on quota violation, brokers send out responses before throttling.
  //
  // Starting in version 3, brokers can send authorized operations.
  //
  // Starting in version 4, the response will optionally include group.instance.id info for members.
  //
  // Version 5 is the first flexible version.
  //
  // Version 6 returns error code GROUP_ID_NOT_FOUND if the group ID is not found (KIP-1043).
  "validVersions": "0-6",
  "flexibleVersions": "6+",
  "fields": [
    { "name": "ThrottleTimeMs", "type": "int32", "versions": "1+", "ignorable": true,
      "about": "The duration in milliseconds for which the request was throttled due to a quota violation, or zero if the request did not violate any quota." },
    { "name": "Groups", "type": "[]DescribedGroup", "versions": "0+",
      "about": "Each described group.", "fields": [
      { "name": "ErrorCode", "type": "int16", "versions": "0+",
        "about": "The describe error, or 0 if there was no error." },
      { "name": "ErrorMessage", "type": "string", "versions": "6+", "nullableVersions": "6+", "default": "null",
        "about": "The describe error message, or null if there was no error." },
      { "name": "GroupId", "type": "string", "versions": "0+", "entityType": "groupId",
        "about": "The group ID string." },
      { "name": "GroupState", "type": "string", "versions": "0+",
        "about": "The group state string, or the empty string." },
      { "name": "ProtocolType", "type": "string", "versions": "0+",
        "about": "The group protocol type, or the empty string." },
      // ProtocolData is currently only filled in if the group state is in the Stable state.
      { "name": "ProtocolData", "type": "string", "versions": "0+",
        "about": "The group protocol data, or the empty string." },
      // N.B. If the group is in the Dead state, the members array will always be empty.
      { "name": "Members", "type": "[]DescribedGroupMember", "versions": "0+",
        "about": "The group members.", "fields": [
        { "name": "MemberId", "type": "string", "versions": "0+",
          "about": "The member ID assigned by the group coordinator." },
        { "name": "GroupInstanceId", "type": "string", "versions": "4+", "ignorable": true,
          "nullableVersions": "4+", "default": "null",
          "about": "The unique identifier of the consumer instance provided by end user." },
        { "name": "ClientId", "type": "string", "versions": "0+",
          "about": "The client ID used in the member's latest join group request." },
        { "name": "ClientHost", "type": "string", "versions": "0+",
          "about": "The client host." },
        // This is currently only provided if the group is in the Stable state.
        { "name": "MemberMetadata", "type": "bytes", "versions": "0+",
          "about": "The metadata corresponding to the current group protocol in use." },
        // This is currently only provided if the group is in the Stable state.
        { "name": "MemberAssignment", "type": "bytes", "versions": "0+",
          "about": "The current assignment provided by the group leader." }
      ]},
      { "name": "AuthorizedOperations", "type": "int32", "versions": "3+",  "default": "-2147483648",
        "about": "32-bit bitfield to represent authorized operations for this group." }
    ]}
  ]
}

Command-line tools

kafka-groups.sh

A new tool called kafka-groups.sh  is introduced for listing groups of any kind. It has the following options:

OptionDescription

--bootstrap-server <String: server to connect to>

REQUIRED: The server(s) to connect to.

--command-config <String: command config property file>

Property file containing configs to be passed to Admin Client.

--consumer

Filters the groups to show all kinds of consumer groups, including classic and simple consumer groups. This matches group type 'consumer', and group type 'classic' where the protocol type is 'consumer' or empty.

--group-type <String: type>

Filters the groups based on group type. Valid types are: 'classic', 'consumer' and 'share'.

--help

Print usage information.

--list

List all groups.

--protocol <String: protocol>

Filters the groups based on protocol type.

--share

Filters the groups to show share groups.

--version

Display Kafka version.

Note that --consumer  actually matches all groups whose type is Consumer , and groups whose type is Classic and protocol type is "consumer", and also "simple" consumer groups whose type is Classic and protocol type is "" . The filtering is done in the kafka-groups.sh  tool.

Here are some examples.

To list all of the groups:

Code Block
$ bin/kafka-groups.sh --bootstrap-server localhost:9092 --list
GROUP                   TYPE          PROTOCOL
old-consumer-group      Classic       consumer
new-consumer-group      Consumer      consumer
connect-cluster         Classic       connect
share-group             Share         share
schema-registry         Classic       sr
simple-consumer-group   Classic

To list all of the consumer groups, silently merging together all of the different kinds of consumer group:

Code Block
$ bin/kafka-groups.sh --bootstrap-server localhost:9092 --list --consumer
GROUP                   TYPE          PROTOCOL
old-consumer-group      Classic       consumer
new-consumer-group      Consumer      consumer
simple-consumer-group   Classic

To list all of the KIP-848 consumer groups

Note that --consumer  actually matches all groups whose type is Consumer , and groups whose type is Classic and protocol type is "consumer", and also "simple" consumer groups whose type is Classic and protocol type is "" . The filtering is done in the kafka-groups.sh  tool.

Here are some examples.

To list all of the groups:

Code Block
$ bin/kafka-groups.sh --bootstrap-server localhost:9092 --list
old-consumer-group
new-consumer-group
connect-cluster
share-group
schema-registry
simple-consumer-group

To describe all of the groups and their types:

Code Block
$ bin/kafka-groups.sh --bootstrap-server localhost:9092 --list --group-describetype consumer
GROUP                   TYPE          PROTOCOL
old-consumer-group      Classic       consumer
new-consumer-group      Consumer      consumer
connect-cluster    

To list all of the share groups:

Code Block
$ bin/kafka-groups.sh --bootstrap-server localhost:9092 --list --share
GROUP     Classic       connect
share-group            TYPE Share         PROTOCOL
share
schema-registry-group             Share  Classic       sr
simple-consumer-group   Classicshare

kafka-consumer-groups.sh

For all operations which act on a single group, if that group exists but is not a consumer group, the command fails with a message indicating that the group type is incorrect, rather than the existing message that the group does not exist.

For example, if you try to describe a share group, the output will look like thisTo list all of the consumer groups:

Code Block
$ bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --listdescribe --consumer
old-consumer-group
new-consumer-group
simple-consumer-groupgroup SG1
Error: Group 'SG1' is not a consumer group.

kafka-share-groups.sh

For all operations which act on a single group, if that group exists but is not a share group, the command fails with a message indicating that the group type is incorrect, rather than the existing message that the group does not exist.

For example, if you try to describe a consumer group, the output will look like thisTo describe all of the consumer groups:

Code Block
$ bin/kafka-groups.sh --bootstrap-server localhost:9092 --describe --consumer
GROUP                   TYPE          PROTOCOL
old-consumer-group      Classic       consumer
new-consumer-group      Consumer      consumer
simple-consumer-group   Classic

To describe all of the KIP-848 consumer groups:

Code Block
$ bin/kafka-groups.sh --bootstrap-server localhost:9092 --describe --group-type consumer
GROUP                   TYPE          PROTOCOL
new-consumer-group      Consumer      consumer

To describe all of the share groups:

Code Block
$ bin/kafka-groups.sh --bootstrap-server localhost:9092 --describe --group-type share
GROUP                   TYPE          PROTOCOL
share-group             Share         share

kafka-consumer-groups.sh

For all operations which act on a single group, if that group exists but is not a consumer group, the command fails with a message indicating that the group type is incorrect, rather than the existing message that the group does not exist.

For example, if you try to describe a share group, the output will look like this:

Code Block
$ bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group SG1
Error: Group 'SG1' is not a consumer group.

kafka-share-groups.sh

For all operations which act on a single group, if that group exists but is not a share group, the command fails with a message indicating that the group type is incorrect, rather than the existing messages that the group does not exist.

For example, if you try to describe a consumer group, the output will look like this:

Code Block
$ bin/kafka-share-groups.sh --bootstrap-server localhost:9092 --describe --group CG1
Error: Group 'CG1' is not a share group.

Compatibility, Deprecation, and Migration Plan

A change to existing behavior is the error messages issued by the kafka-consumer-groups.sh  and kafka-share-groups.sh  tools when working with groups of the wrong type.

We also need to consider compatibility for the ListGroups RPC and the AdminClient.listGroups() method. ListGroups v5 introduces the TypesFilter  in the request and the GroupType  in the response. This KIP does not introduce a new version of the ListGroups RPC.

  • If the broker does not support ListGroups v5, setting a types filter using ListGroupOptions.withTypes() results in UnsupportedVersionException when the ListGroups request is serialized. (This is KIP-848 behavior.)
  • If the broker receives a group type in TypesFilter  that it does not support in a ListGroups v5 (or later) request, the filter is treated as unknown group type which thus matches no groups. (This is KIP-848 behavior.)
  • If the client receives a group type that it does not understand in a ListGroups v5 (or later) response, the group type is GroupType.UNKNOWN . This is because the group type in the ListGroups RPC response cannot be parsed into a value of the GroupType  enumeration, and the default value of UNKNOWN  is used in these situations.

As a result, there is no compatibility problem with ListGroups as a result of this KIP.

Test Plan

The feature will be thoroughly tested with unit and integration tests.

Rejected Alternatives

share-groups.sh --bootstrap-server localhost:9092 --describe --group CG1
Error: Group 'CG1' is not a share group.

Compatibility, Deprecation, and Migration Plan

ListGroups RPC

When writing this KIP, it seemed that perhaps the ListGroups RPC would need to be enhanced to create the Admin.listGroups()  method.

ListGroups v5 introduces the TypesFilter  in the request and the GroupType  in the response. This KIP does not need to introduce a new version of the ListGroups RPC, because:

  • If the broker does not support ListGroups v5, setting a types filter using ListGroupOptions.withTypes() results in UnsupportedVersionException when the ListGroups request is serialized. (This is KIP-848 behavior.)
  • If the broker receives a group type in TypesFilter  that it does not support in a ListGroups v5 (or later) request, the filter is treated as unknown group type which thus matches no groups. (This is KIP-848 behavior.)
  • If the client receives a group type that it does not understand in a ListGroups v5 (or later) response, the group type is GroupType.UNKNOWN . This is because the group type in the ListGroups RPC response cannot be parsed into a value of the GroupType  enumeration, and the default value of UNKNOWN  is used in these situations.

The existing behavior suffices and there is no compatibility problem.

DescribeConsumerGroups

Prior to this KIP, the behavior of Admin.describeConsumerGroups(Collection<String>) seems a little unusual when used with groups which are not consumer groups. You can describe a collection of group IDs, some of which might exist and others might not. Here's how the response is built:

  1. If the group is a consumer group and the client is authorized to describe the group and there was no error, the group information is returned, along with the authorized operations if requested.
  2. If the group is not a consumer group (either does not exist or wrong type) and the client is authorized to describe the group and there was no error, the group information for a dead group is returned, along with the authorized operations if requested.
  3. If the client is not authorized to the describe the group, the group information error code is set to GROUP_AUTHORIZATION_FAILED . 
  4. If there was an error describing the group, the group information error code is set.

In cases (1) and (2), the admin client considers the operation a success, and this means the KafkaFuture for this group completes successfully. In cases (3) and (4), the admin client considers the operation unsuccessful, and this means the KafkaFuture for this group completes exceptionally. Case (2) is the tricky one because you can't readily tell what the dead group means. This is why the admin tools use output like Error: Consumer group 'MYSHARE" does not exist, even when the group ID is recognised and it's just the wrong type.

After this KIP, if you use Admin.describeConsumerGroup(Collection<String>)  to describe a group which is not a consumer group, case (2) above will result in the group information error code set to GROUP_ID_NOT_FOUND . In the admin client, this means the future for this group completes exceptionally with GroupIdNotFoundException  rather than succeeding with a dead consumer group.

Test Plan

The feature will be thoroughly tested with unit and integration tests.

Rejected Alternatives

It would be possible to preserve the current behavior of Admin.describeConsumerGroups(Collection<String>)  when used with a group which is not a consumer group and introduce an option to ask it to validate the group type rather than converting any indescribable group into a dead consumer group. This seems like an unnecessary complication with little benefit.

It was proposed to add a new error code INCONSISTENT_GROUP_TYPE  to indicate that the group existed but the group type was inconsistent with the operation. Instead, the existing error code GROUP_ID_NOT_FOUND  is used and the protocol is enhanced so that an error message is returned along with this error codeNone.