Skip to content

[feat](protocol)extract protocol definitions (part 1) - #60355

Closed
CalvinKirs wants to merge 13 commits into
apache:masterfrom
CalvinKirs:master-new-protocol
Closed

[feat](protocol)extract protocol definitions (part 1)#60355
CalvinKirs wants to merge 13 commits into
apache:masterfrom
CalvinKirs:master-new-protocol

Conversation

@CalvinKirs

@CalvinKirsCalvinKirs commented Jan 29, 2026

Copy link
Copy Markdown
Member

What problem does this PR solve?

#60361

Overview

This document describes Apache Doris multi-protocol support. The architecture uses SPI
(Service Provider Interface) to decouple protocol implementations from the kernel.

Core Design

SPI Loading Flow

┌─────────────────────────────────────────────────────────────────────────┐
│ QeService (fe-core) │
│ │
│ 1. Build ProtocolConfig (from Config + FrontendOptions) │
│ 2. ProtocolLoader.loadConfiguredProtocols(config) │
│ 3. Set protocol acceptor callbacks │
│ 4. handler.start() to launch protocol servers │
└────────────────────────────────────────────────────────────┬─────────────┘
│
ServiceLoader discovery │
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ META-INF/services/org.apache.doris.protocol.ProtocolHandler │
│ │
│ org.apache.doris.mysql.MysqlProtocolHandler │
│ org.apache.doris.protocol.arrowflight.ArrowFlightProtocolHandler │
└────────────────────────────────────────────────────────────┬─────────────┘
│
instantiate + initialize │
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ MysqlProtocolHandler (fe-protocol-mysql) │
│ │
│ - initialize(config): read port, ioThreads, backlog, etc │
│ - setAcceptor(callback): receive fe-core connection handling callback │
│ - start(): create XNIO server, start listening │
│ - on new connection: acceptor.accept(connection) │
└─────────────────────────────────────────────────────────────────────────┘

Configuration Flow

fe-core builds a ProtocolConfig and passes it into protocol handlers:

// QeService.javaprivateProtocolConfigbuildProtocolConfig() {
ProtocolConfigconfig = newProtocolConfig(mysqlPort, arrowFlightPort, scheduler);
// From Configconfig.set(KEY_MYSQL_IO_THREADS, Config.mysql_service_io_threads_num);
config.set(KEY_MYSQL_BACKLOG, Config.mysql_nio_backlog_num);
config.set(KEY_MYSQL_KEEP_ALIVE, Config.mysql_nio_enable_keep_alive);
// From FrontendOptionsconfig.set(KEY_MYSQL_BIND_IPV6, FrontendOptions.isBindIPV6());
// External thread poolconfig.set(KEY_MYSQL_TASK_EXECUTOR, ThreadPoolManager.newDaemon...());
returnconfig;
}

Configuration Key Mapping

fe-core parameterProtocolConfig keyNotes
Config.mysql_service_io_threads_nummysql.io.threadsIO threads (default 4)
Config.mysql_nio_backlog_nummysql.backlogbacklog size (default 1024)
Config.mysql_nio_enable_keep_alivemysql.keep.aliveTCP keep-alive
FrontendOptions.isBindIPV6()mysql.bind.ipv6bind IPv6
Config.max_mysql_service_task_threads_nummysql.max.task.threadsmax task threads
ThreadPoolManager executormysql.task.executorexternal thread pool

Design Principles

  1. Protocol modules are independent

    • Each protocol (MySQL, Arrow Flight) is a standalone Java module.
    • Modules do not depend on each other.
  2. Kernel is decoupled from protocols

    • Kernel uses abstract SPI interfaces only.
    • Protocol modules implement SPI and provide services.
  3. Shared API can evolve

    • SPI and shared API can expand for new protocols/features.
  4. Protocol compatibility must not break

    • MySQL protocol packet format, crypto, and handshake must remain compatible.
  5. SPI extension mechanism

    • New protocols are discovered via SPI; kernel does not hardcode implementations.

Migration Status

Classes already moved to protocol modules

Protocol module classOriginal fe-core classNotes
o.a.d.protocol.mysql.MysqlCapabilityo.a.d.mysql.MysqlCapabilitycapability flags
o.a.d.protocol.mysql.MysqlCommando.a.d.mysql.MysqlCommandcommand enum
o.a.d.protocol.mysql.MysqlServerStatusFlago.a.d.mysql.MysqlServerStatusFlagserver status
o.a.d.protocol.mysql.MysqlColTypeo.a.d.catalog.MysqlColTypeMySQL type codes
o.a.d.protocol.mysql.MysqlPacketo.a.d.mysql.MysqlPacketbase packet
o.a.d.protocol.mysql.MysqlHandshakePacketo.a.d.mysql.MysqlHandshakePackethandshake
o.a.d.protocol.mysql.MysqlAuthPacketo.a.d.mysql.MysqlAuthPacketauth packet
o.a.d.protocol.mysql.MysqlAuthSwitchPacketo.a.d.mysql.MysqlAuthSwitchPacketauth switch
o.a.d.protocol.mysql.MysqlOkPacketo.a.d.mysql.MysqlOkPacketOK packet
o.a.d.protocol.mysql.MysqlErrPacketo.a.d.mysql.MysqlErrPacketerror packet
o.a.d.protocol.mysql.MysqlEofPacketo.a.d.mysql.MysqlEofPacketEOF packet
o.a.d.protocol.mysql.MysqlClearTextPacketo.a.d.mysql.MysqlClearTextPacketclear text
o.a.d.protocol.mysql.MysqlSslPacketo.a.d.mysql.MysqlSslPacketSSL request
o.a.d.protocol.mysql.MysqlColDefo.a.d.mysql.MysqlColDefcolumn definition
o.a.d.protocol.mysql.FieldInfoo.a.d.mysql.FieldInfofield metadata
o.a.d.protocol.mysql.MysqlSerializero.a.d.mysql.MysqlSerializerserializer
o.a.d.protocol.mysql.MysqlProtoo.a.d.mysql.MysqlProtoprotocol utils
o.a.d.protocol.mysql.MysqlPasswordo.a.d.mysql.MysqlPasswordpassword crypto
o.a.d.protocol.mysql.BytesChannelo.a.d.mysql.BytesChannelchannel interface
o.a.d.protocol.mysql.SslEngineHelpero.a.d.mysql.SslEngineHelperSSL utilities

Classes still in fe-core (kernel dependencies)

These remain in fe-core due to heavy coupling with kernel classes (Config,
ConnectContext, QueryState, Auth, etc):

ClassDependenciesReason
MysqlChannelConnectContextconnection context needed
MysqlSslContextConfigSSL configuration
MysqlProto.negotiate()ConnectContext, Env, Authauth/handshake logic
MysqlSerializer.writeField()Typeresult serialization with kernel types
ReadListenerConnectContext, ConnectProcessorquery pipeline
ProxyProtocolHandlerkernel classesproxy protocol support
authenticate/privilegesauth logic
privilege/privilegespermission checks

Why full decoupling is still hard

  1. Connection lifecycle
    • ConnectContext owns session state, variables, transaction state.
  2. Auth and privilege checks
    • MySQL handshake calls Env.getAuth() directly.
  3. Query execution
    • ConnectProcessor drives parsing, planning, execution.
  4. Result serialization
    • MysqlSerializer.writeField() maps kernel Type to MySQL types.

Current Decoupling Strategy

┌─────────────────────────────────────────────────────────────────────────┐
│ fe-protocol-mysql (decoupled) │
│ │
│ OK Network layer: XNIO server, acceptor │
│ OK Protocol definitions: packets, fields, command codes │
│ OK Base serialization and crypto │
│ OK Handshake/Auth packets, OK/Err/EOF packets │
└─────────────────────────────────────────────────────────────────────────┘
↑
acceptor callback
│
┌─────────────────────────────────────────────────────────────────────────┐
│ fe-core (still coupled) │
│ │
│ WARN Connection handling: MysqlChannel, ReadListener │
│ WARN Protocol negotiation: MysqlProto.negotiate() │
│ WARN Query processing: ConnectProcessor │
│ WARN Result serialization: MysqlSerializer.writeField(Type) │
│ WARN Auth/privilege: authenticate/, privilege/ │
└─────────────────────────────────────────────────────────────────────────┘

Module Layout

fe/
├── fe-common/ # shared utilities
├── fe-protocol/ # protocol parent
│ ├── fe-protocol-api/ # SPI interfaces
│ │ └── org.apache.doris.protocol/
│ │ ├── ProtocolHandler.java
│ │ ├── ProtocolConfig.java
│ │ ├── ProtocolContext.java
│ │ ├── ProtocolLoader.java
│ │ ├── ProtocolException.java
│ │ └── AuthenticationResult.java
│ │
│ ├── fe-protocol-mysql/ # MySQL protocol implementation
│ │ └── org.apache.doris.protocol.mysql/
│ │ ├── MysqlProtocolHandler.java
│ │ ├── MysqlProto.java
│ │ ├── command/
│ │ │ └── MysqlCommand.java
│ │ └── channel/
│ │ └── MysqlChannel.java
│ │
│ └── fe-protocol-arrowflight/ # Arrow Flight SQL implementation
│ └── org.apache.doris.protocol.arrowflight/
│ ├── ArrowFlightProtocolHandler.java
│ └── FlightSqlContext.java
│
└── fe-core/ # kernel
└── org.apache.doris.qe/
└── QeService.java # loads protocols via SPI

Module Dependencies

 ┌─────────────────────────────────────┐
│ fe-common │
│ (shared utilities) │
└─────────────────────────────────────┘
▲
┌───────────────────────┼───────────────────────┐
│ │ │
│ │ │
┌───────────┴───────────┐ │ ┌───────────┴───────────┐
│ fe-protocol-api │ │ │ fe-protocol-api │
│ (SPI) │ │ │ (SPI) │
└───────────────────────┘ │ └───────────────────────┘
▲ │ ▲
│ │ │
┌───────────┴───────────┐ ┌────────┴────────┐ ┌───────────┴───────────┐
│ fe-protocol-mysql │ │ fe-core │ │ fe-protocol-arrowflight│
│ (MySQL impl) │◄──│ (kernel) │──►│ (Arrow Flight impl) │
└───────────────────────┘ └─────────────────┘ └───────────────────────┘

SPI Interfaces

ProtocolHandler

publicinterfaceProtocolHandler {
StringgetProtocolName();
StringgetProtocolVersion();
voidinitialize(ProtocolConfigconfig) throwsProtocolException;
voidsetAcceptor(Consumer<Object> acceptor);
booleanstart();
voidstop();
booleanisRunning();
intgetPort();
booleanisEnabled(ProtocolConfigconfig);
intgetPriority();
}

ProtocolContext

publicinterfaceProtocolContext {
StringgetProtocolName();
longgetConnectionId();
StringgetRemoteIP();
StringgetUser();
StringgetDatabase();
voidsetDatabase(Stringdatabase);
booleanisAuthenticated();
booleanisKilled();
voidsetKilled();
voidcleanup();
<T> TgetChannel();
}

Kernel Usage Example

QeService loads protocol handlers via SPI and registers acceptors:

publicclassQeService {
publicQeService(intmysqlPort, intarrowFlightPort, ConnectSchedulerscheduler) {
ProtocolConfigconfig = buildProtocolConfig();
List<ProtocolHandler> handlers = ProtocolLoader.loadConfiguredProtocols(config);
for (ProtocolHandlerhandler : handlers) {
if ("mysql".equalsIgnoreCase(handler.getProtocolName())) {
handler.setAcceptor(this::handleMysqlConnection);
} elseif ("arrowflight".equalsIgnoreCase(handler.getProtocolName())) {
handler.setAcceptor(this::handleArrowFlightConnection);
}
protocolHandlers.add(handler);
}
}
}

Add a New Protocol

  1. Create a new module:
mkdir -p fe/fe-protocol/fe-protocol-newprotocol/src/main/java/org/apache/doris/protocol/newprotocol
mkdir -p fe/fe-protocol/fe-protocol-newprotocol/src/main/resources/META-INF/services
  1. Implement ProtocolHandler:
packageorg.apache.doris.protocol.newprotocol;
publicclassNewProtocolHandlerimplementsProtocolHandler {
@OverridepublicStringgetProtocolName() { return"newprotocol"; }
@Overridepublicvoidinitialize(ProtocolConfigconfig) throwsProtocolException { }
@Overridepublicbooleanstart() { returntrue; }
}
  1. Register SPI service:

META-INF/services/org.apache.doris.protocol.ProtocolHandler

org.apache.doris.protocol.newprotocol.NewProtocolHandler
  1. Add module pom and dependency on fe-protocol-api.

  2. Add dependency in fe-core to load the new protocol.

Arrow Flight: Current Coupling

Arrow Flight SQL is still tightly coupled with fe-core:

  • QeService constructs Arrow Flight objects (FlightTokenManagerImpl,
    DorisFlightSqlProducer, FlightBearerTokenAuthenticator) and injects them
    into ProtocolConfig, which mixes protocol-specific runtime objects into the
    kernel config path.
  • DorisFlightSqlService is still launched by QeService when SPI handler is not
    present (legacy fallback).
  • FlightSqlConnectProcessor and FlightSqlConnectContext still depend on
    ConnectContext, ConnectScheduler, Auth, and execution pipeline classes.
  • ConnectScheduler owns FlightSqlConnectPoolMgr, used by session/token logic
    and by connection limit enforcement.

Backward Compatibility

MySQL Protocol Compatibility

  1. Packet formats unchanged
  2. Crypto/SSL unchanged
  3. Handshake flow unchanged
  4. Command coverage unchanged

Migration Strategy

  1. Keep existing org.apache.doris.mysql.* package as-is
  2. Protocol modules can delegate to legacy implementation
  3. Kernel calls protocols via SPI
  4. Gradual migration without breaking clients

Configuration Parameters

ParameterDescriptionDefault
query_portMySQL protocol port9030
arrow_flight_sql_portArrow Flight SQL port9090
enable_sslenable SSLfalse
max_connection_scheduler_threads_nummax connections4096

References

  • MySQL Protocol Documentation
  • Arrow Flight SQL Specification
  • Java ServiceLoader

TODO / Next Steps

The current protocol split is incomplete. Coupling remains high in user/session
management, configuration wiring, and connection pool management. The following
items are the prioritized next steps:

  1. Separate user/session management from protocol handlers

    • Introduce a kernel-facing AuthenticationService / SessionService SPI so
      protocol modules do not call Env.getAuth() or access user limits directly.
    • Move user identity, session variables, and per-user limits into a protocol-
      neutral service. Protocols should only pass credentials and connection info.
  2. Decouple configuration and parameter wiring

    • Replace direct Config/FrontendOptions reads inside QeService with a
      dedicated ProtocolConfigFactory that builds protocol-scoped configs.
    • Avoid injecting protocol-specific runtime objects (token managers, producers,
      executors) into ProtocolConfig. Instead, let protocol modules create and
      own these objects behind SPI boundaries, or supply them via dedicated SPI
      providers.
  3. Extract connection pool management

    • Move ConnectPoolMgr and FlightSqlConnectPoolMgr behind a unified
      ConnectionPoolService in fe-core.
    • Protocol handlers should register/unregister connections through the SPI
      service rather than reaching into ConnectScheduler directly.
  4. Define connection lifecycle SPI

    • Standardize onConnect, onAuthenticate, onQuery, onClose hooks so
      MySQL and Arrow Flight share a consistent lifecycle, and kernel code owns
      the execution pipeline.
  5. Finish Arrow Flight migration

    • Move DorisFlightSqlService startup and token/session management into
      fe-protocol-arrowflight.
    • Remove the legacy fallback path from QeService after migration.
  6. Reduce kernel type leakage

    • Introduce a protocol-neutral type mapping layer to reduce direct dependency
      on Type in serializers.
  7. Add tests for SPI wiring

    • Validate handler discovery, config mapping, and connection lifecycle for
      each protocol module.

@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@CalvinKirs

Copy link
Copy Markdown
MemberAuthor

run buildall

@CalvinKirs

Copy link
Copy Markdown
MemberAuthor

run buildall

@CalvinKirs

Copy link
Copy Markdown
MemberAuthor

run buildall

1 similar comment
@CalvinKirs

Copy link
Copy Markdown
MemberAuthor

run buildall

@CalvinKirs

Copy link
Copy Markdown
MemberAuthor

run buildall

When an insert task is canceled during execution, the shared `ctx` object may be set to null.
Currently, after `command.runWithUpdateInfo(...)`, the code directly accesses `ctx.getState()`.
If `ctx` is null or its state is null, this leads to a NullPointerException (NPE).
### Solution
- Cache the `ctx` reference to a local variable to avoid race conditions.
- Check both `ctx` and `ctx.getState()` for null before accessing.
- If the task was canceled or the state is null, safely return without throwing an exception.
- Maintain existing behavior: if the task completes with a non-OK state, still throw `JobException`.
### Impact
- Prevents NPE when a task is canceled during execution.
- Makes the insert task more robust in concurrent cancel scenarios.
@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 10.37% (31/299) 🎉
Increment coverage report
Complete coverage report

@CalvinKirs

Copy link
Copy Markdown
MemberAuthor

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 10.13% (31/306) 🎉
Increment coverage report
Complete coverage report

@github-actions

Copy link
Copy Markdown
Contributor

We're closing this PR because it hasn't been updated in a while.
This isn't a judgement on the merit of the PR in any way. It's just a way of keeping the PR queue manageable.
If you'd like to revive this PR, please reopen it and feel free a maintainer to remove the Stale tag!

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@CalvinKirs@hello-stephen@morningman