Skip to content

Commit f361949

Browse files
authored
Merge pull request #105 from kafka4beam/251024-stream-line-message-encoding
251024 streamline message encoding
2 parents fcb9876 + 9ec0ae9 commit f361949

3 files changed

Lines changed: 95 additions & 21 deletions

File tree

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
- Added producer process label `{wolff_producer, KafkaClientId, Topic, Partition}` [#102](https://github.com/kafka4beam/wolff/pull/102)
44
- Upgrade to `kafka_protocol-4.3.0` for better CRC32C performance.
55
- Add client config `allow_auto_topic_creation` (default = false).
6+
- Streamline batch encoding.
67

78
* 4.0.13 (merge 1.5.19)
89
- Handle `record_list_too_large` error returned from Kafka.

src/wolff_producer.erl

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
, inflight_calls := non_neg_integer()
128128
, topic := topic()
129129
, calls := ?EMPTY | calls()
130+
, sender => pid()
130131
}.
131132

132133
%% @doc Start a per-partition producer worker.
@@ -231,7 +232,8 @@ send_sync(Pid, Batch0, Timeout) ->
231232
init(#{client_id := ClientId, topic := Topic, partition := Partition} = St) ->
232233
erlang:process_flag(trap_exit, true),
233234
ok = set_process_label(ClientId, Topic, Partition),
234-
{ok, St, {continue, do_init}}.
235+
{ok, Sender} = wolff_sender:start_link(self(), ClientId, Topic, Partition),
236+
{ok, St#{sender => Sender}, {continue, do_init}}.
235237

236238
do_init(#{client_id := ClientId,
237239
topic := Topic,
@@ -423,10 +425,12 @@ use_defaults(Config, [{K, V} | Rest]) ->
423425
false -> use_defaults(Config#{K => V}, Rest)
424426
end.
425427

426-
resend_requests(#{q_items := Items} = Sent, St, ?reconnect) ->
428+
resend_requests(#{q_items := Items, req_ref := {alias, OldRef}} = Sent, St, ?reconnect) ->
429+
erlang:unalias(OldRef),
427430
{ok, Ref} = send_request(Items, St),
428431
[Sent#{req_ref := Ref}];
429-
resend_requests(#{q_items := Items} = Sent, St, ?message_too_large) ->
432+
resend_requests(#{q_items := Items, req_ref := {alias, OldRef}} = Sent, St, ?message_too_large) ->
433+
erlang:unalias(OldRef),
430434
Refs = lists:map(fun(Item) -> {ok, Ref} = send_request([Item], St), Ref end, Items),
431435
#{attempts := Attempts, q_ack_ref := Q_AckRef} = Sent,
432436
make_sent_items_list(Items, Refs, Attempts, Q_AckRef).
@@ -445,25 +449,25 @@ make_sent_items_list([Item | Items], [Ref | Refs], Attempts, Q_AckRef) ->
445449
attempts => Attempts
446450
} | make_sent_items_list(Items, Refs, Attempts, Q_AckRef)].
447451

448-
send_request(QueueItems, #{conn := Conn} = St) ->
449-
#kpro_req{ref = Ref} = Req = make_request(QueueItems, St),
450-
ok = request_async(Conn, Req),
451-
{ok, Ref}.
452-
453-
make_request(QueueItems,
452+
send_request(QueueItems,
454453
#{config := #{required_acks := RequiredAcks,
455454
ack_timeout := AckTimeout,
456455
compression := Compression
457456
},
458457
produce_api_vsn := Vsn,
459458
topic := Topic,
460-
partition := Partition}) ->
459+
partition := Partition,
460+
conn := Conn,
461+
sender := Sender
462+
}) ->
461463
Batch = get_batch_from_queue_items(QueueItems),
462-
kpro_req_lib:produce(Vsn, Topic, Partition, Batch,
463-
#{ack_timeout => AckTimeout,
464-
required_acks => RequiredAcks,
465-
compression => Compression
466-
}).
464+
Opts = #{ack_timeout => AckTimeout,
465+
required_acks => RequiredAcks,
466+
compression => Compression
467+
},
468+
Ref = {alias, erlang:alias([reply])},
469+
ok = wolff_sender:do(Sender, Conn, Ref, Vsn, Topic, Partition, Batch, Opts),
470+
{ok, Ref}.
467471

468472
resend_sent_reqs(#{sent_reqs := SentReqs,
469473
config := Config
@@ -504,7 +508,6 @@ send_to_kafka(#{sent_reqs := SentReqs,
504508
replayq := Q,
505509
config := #{required_acks := RequiredAcks,
506510
max_batch_bytes := BytesLimit} = Config,
507-
conn := Conn,
508511
pending_acks := PendingAcks
509512
} = St0) ->
510513
{NewQ, QAckRef, Items} =
@@ -518,7 +521,7 @@ send_to_kafka(#{sent_reqs := SentReqs,
518521
NewInflightCalls = InflightCalls + NrOfCalls,
519522
_ = wolff_metrics:inflight_set(Config, NewInflightCalls),
520523
NoAck = (RequiredAcks =:= none),
521-
#kpro_req{ref = Ref} = Req = make_request(Items, St0),
524+
{ok, Ref} = send_request(Items, St0),
522525
Sent = #{req_ref => Ref,
523526
q_items => Items,
524527
q_ack_ref => QAckRef,
@@ -530,7 +533,6 @@ send_to_kafka(#{sent_reqs := SentReqs,
530533
inflight_calls := NewInflightCalls,
531534
pending_acks := NewPendingAcks
532535
},
533-
ok = request_async(Conn, Req),
534536
St2 = maybe_fake_kafka_ack(NoAck, St1),
535537
maybe_send_to_kafka(St2).
536538

@@ -876,9 +878,6 @@ varint_bytes(N) -> vb(zz(N)).
876878
-compile({inline, zz/1}).
877879
zz(I) -> (I bsl 1) bxor (I bsr 63).
878880

879-
request_async(Conn, Req) when is_pid(Conn) ->
880-
ok = kpro:send(Conn, Req).
881-
882881
%% collect send calls which are already sent to mailbox,
883882
%% the collection is size-limited by the max_linger_bytes config.
884883
collect_send_calls(Call, Bytes, ?EMPTY, Max) ->

src/wolff_sender.erl

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
%% Copyright (c) 2025 EMQ Technologies Co., Ltd. All Rights Reserved.
2+
%%
3+
%% Licensed under the Apache License, Version 2.0 (the "License");
4+
%% you may not use this file except in compliance with the License.
5+
%% You may obtain a copy of the License at
6+
%%
7+
%% http://www.apache.org/licenses/LICENSE-2.0
8+
%%
9+
%% Unless required by applicable law or agreed to in writing, software
10+
%% distributed under the License is distributed on an "AS IS" BASIS,
11+
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
%% See the License for the specific language governing permissions and
13+
%% limitations under the License.
14+
15+
%% This module encodes input batch into Kafka produce request and send
16+
%% the encoded batch to kpro_connection process.
17+
%% It offloads the heavy lifting work:
18+
%% - Batch encoding
19+
%% - CRC32C
20+
%% - Compression
21+
%% From wolff_producer process.
22+
-module(wolff_sender).
23+
24+
-behaviour(gen_server).
25+
26+
-export([start_link/4, do/8]).
27+
28+
-include_lib("kafka_protocol/include/kpro.hrl").
29+
30+
%% gen_server callbacks
31+
-export([code_change/3, handle_call/3, handle_cast/2, handle_info/2, init/1, terminate/2]).
32+
33+
-spec do(pid(), pid(), {alias, reference()}, kpro:vsn(),
34+
kpro:topic(), kpro:partition(), [kpro:msg_input()], kpro:produce_opts()) -> ok.
35+
do(SenderPid, ConnPid, Ref, Vsn, Topic, Partition, Batch, Opts) ->
36+
gen_server:cast(SenderPid, {send, ConnPid, Ref, Vsn, Topic, Partition, Batch, Opts}).
37+
38+
%% @doc Start the wolff_sender gen_server.
39+
-spec start_link(pid(), wolff:client_id(), kpro:topic(), kpro:partition()) -> {ok, pid()} | {error, any()}.
40+
start_link(Owner, ClientId, Topic, Partition) ->
41+
gen_server:start_link(?MODULE, {Owner, ClientId, Topic, Partition}, []).
42+
43+
%% gen_server callbacks
44+
45+
init({Owner, ClientId, Topic, Partition}) ->
46+
ok = set_process_label(ClientId, Topic, Partition),
47+
{ok, #{owner => Owner, client_id => ClientId, topic => Topic, partition => Partition}}.
48+
49+
handle_call(_Request, _From, State) ->
50+
{reply, {error, unknown_request}, State}.
51+
52+
handle_cast({send, ConnPid, Ref, Vsn, Topic, Partition, Batch, Opts}, State) ->
53+
Req = kpro_req_lib:produce(Vsn, Topic, Partition, Batch, Opts, Ref),
54+
ok = kpro:send(ConnPid, Req),
55+
{noreply, State};
56+
handle_cast(_Msg, State) ->
57+
{noreply, State}.
58+
59+
handle_info(_Info, State) ->
60+
{noreply, State}.
61+
62+
terminate(_Reason, _State) ->
63+
ok.
64+
65+
code_change(_OldVsn, State, _Extra) ->
66+
{ok, State}.
67+
68+
-if(?OTP_RELEASE >= 27).
69+
set_process_label(ClientId, Topic, Partition) ->
70+
proc_lib:set_label({?MODULE, ClientId, Topic, Partition}).
71+
-else.
72+
set_process_label(_ClientId, _Topic, _Partition) ->
73+
ok.
74+
-endif.

0 commit comments

Comments
 (0)