Skip to content

Commit d325e21

Browse files
authored
Merge pull request #100 from zmstone/250910-handle-record_list_too_large
fix: handle record_list_too_large error in produce response
2 parents 83b2dbc + 85de04f commit d325e21

3 files changed

Lines changed: 60 additions & 22 deletions

File tree

changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
* 1.5.19
2+
- Handle `record_list_too_large` error returned from Kafka.
3+
Similar to `message_too_large` error, the batch is split, then dropped if single call is still too large.
14
* 1.5.18
25
- Partition metadata handling.
36
- Fixed an issue introduced in 1.5.15 where temporarily missing partitions in the metadata response could leave a `wolff_producer` process permanently disconnected.

src/wolff_producer.erl

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,8 @@ handle_kafka_ack(#kpro_rsp{api = produce,
492492
do_handle_kafka_ack(Ref, BaseOffset, St, normal);
493493
?message_too_large ->
494494
do_handle_kafka_ack(Ref, BaseOffset, St, ?message_too_large);
495+
?record_list_too_large ->
496+
do_handle_kafka_ack(Ref, BaseOffset, St, ?record_list_too_large);
495497
_ ->
496498
#{topic := Topic, partition := Partition} = St,
497499
log_warn(Topic, Partition, "Produce response error-code = ~0p", [ErrorCode]),
@@ -503,16 +505,20 @@ do_handle_kafka_ack(Ref, BaseOffset, #{sent_reqs := SentReqs } = St, Reason) ->
503505
{value, ?sent_req(#kpro_req{ref = Ref}, Q_AckRef, Calls)} ->
504506
%% this clause is kept only to be hot-upgrade safe
505507
clear_sent_and_ack_callers(Q_AckRef, Calls, BaseOffset, St);
506-
{value, ?sent_items(Ref, Items, Q_AckRef)} when Reason =:= ?message_too_large ->
508+
{value, ?sent_items(Ref, Items, Q_AckRef)} when Reason =:= ?message_too_large orelse Reason =:= ?record_list_too_large ->
507509
case Items of
508510
[?Q_ITEM(_CallId, _Ts, Batch)] ->
509511
%% This is one call, but it's still too large
510512
#{topic := Topic, partition := Partition} = St,
511513
#kpro_req{msg = IoData} = make_request(Items, St),
512514
EncodedBytes = iolist_size(IoData),
513-
log_error(Topic, Partition, "One request is dropped due to message_too_large! "
514-
"This single-request includes ~p message(s), and encoded to ~p bytes. "
515-
"Please consider increasing max.message.bytes on the server side!",
515+
Hint = case Reason of
516+
?message_too_large ->
517+
"Consider increasing server side topic config 'max.message.bytes'!";
518+
?record_list_too_large ->
519+
"Cnosider increasing server side topic config 'segment.bytes'!"
520+
end,
521+
log_error(Topic, Partition, "Produce request dropped (with ~w messages encoded to ~w bytes). " ++ Hint,
516522
[length(Batch), EncodedBytes]),
517523
Calls = get_calls_from_queue_items(Items),
518524
clear_sent_and_ack_callers(Q_AckRef, Calls, ?message_too_large, St);

test/wolff_tests.erl

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,10 @@ test_leader_restart() ->
445445
end).
446446

447447
with_topic(Topic, Partitions, ReplicationFactor, MaxMessageBytes, TestFunc) ->
448-
ok = create_topic(Topic, Partitions, ReplicationFactor, MaxMessageBytes),
448+
with_topic(Topic, Partitions, ReplicationFactor, MaxMessageBytes, default_sgement_bytes, TestFunc).
449+
450+
with_topic(Topic, Partitions, ReplicationFactor, MaxMessageBytes, SegmentBytes, TestFunc) ->
451+
ok = create_topic(Topic, Partitions, ReplicationFactor, MaxMessageBytes, SegmentBytes),
449452
try
450453
_ = application:stop(wolff), %% ensure stopped
451454
{ok, _} = application:ensure_all_started(wolff),
@@ -454,23 +457,13 @@ with_topic(Topic, Partitions, ReplicationFactor, MaxMessageBytes, TestFunc) ->
454457
ok = delete_topic(Topic)
455458
end.
456459

457-
458-
message_too_large_test_() ->
459-
{timeout, 60,
460-
fun() -> test_message_too_large() end}.
461-
462-
test_message_too_large() ->
463-
Topic = "message-too-large-" ++ integer_to_list(abs(erlang:monotonic_time())),
464-
Partitions = 1,
465-
ReplicationFactor = 1,
466-
MaxMessageBytes = 100,
467-
with_topic(Topic, Partitions, ReplicationFactor, MaxMessageBytes, fun() ->
460+
test_batch_split_then_drop(Topic, MaxMessageBytes) ->
468461
ClientCfg = client_config(),
469462
ClientId = iolist_to_binary("client-" ++ Topic),
470463
{ok, Client} = start_client(ClientId, ?HOSTS, ClientCfg#{connection_strategy => per_partition}),
471464
TopicBin = iolist_to_binary(Topic),
472465
%% try to batch more messages than Kafka's limit,
473-
%% the producer will get message_too_large error back
466+
%% the producer will get message_too_large or record_list_too_large error back
474467
%% then it should retry sending one message at a time
475468
ProducerCfg = #{partitioner => fun(_, _) -> 0 end,
476469
max_batch_bytes => MaxMessageBytes * 3,
@@ -504,8 +497,38 @@ test_message_too_large() ->
504497
?assertEqual(message_too_large, (SendFunc([Msg(<<"0123456789">>)]))()),
505498
ok = wolff:stop_producers(Producers),
506499
ok = stop_client(Client),
507-
ok = application:stop(wolff)
508-
end).
500+
ok = application:stop(wolff).
501+
502+
%% Max message size is smaller than segment bytes to tigger record_list_too_large error.
503+
%% This is usually a bad server/topic configuration, but we need to cover it anyways.
504+
record_list_too_large_test_() ->
505+
{timeout, 60,
506+
fun() -> test_record_list_too_large() end}.
507+
508+
test_record_list_too_large() ->
509+
Topic = "record-list-too-large-" ++ integer_to_list(abs(erlang:monotonic_time())),
510+
Partitions = 1,
511+
ReplicationFactor = 1,
512+
MaxMessageBytes = 1000,
513+
SegmentBytes = 100,
514+
with_topic(Topic, Partitions, ReplicationFactor, MaxMessageBytes, SegmentBytes,
515+
fun() ->
516+
test_batch_split_then_drop(Topic, SegmentBytes)
517+
end).
518+
519+
message_too_large_test_() ->
520+
{timeout, 60,
521+
fun() -> test_message_too_large() end}.
522+
523+
test_message_too_large() ->
524+
Topic = "message-too-large-" ++ integer_to_list(abs(erlang:monotonic_time())),
525+
Partitions = 1,
526+
ReplicationFactor = 1,
527+
MaxMessageBytes = 100,
528+
with_topic(Topic, Partitions, ReplicationFactor, MaxMessageBytes,
529+
fun() ->
530+
test_batch_split_then_drop(Topic, MaxMessageBytes)
531+
end).
509532

510533
one_byte_limit_test() ->
511534
Topic = "one-byte-limit-" ++ integer_to_list(abs(erlang:monotonic_time())),
@@ -616,8 +639,8 @@ encoded_bytes(Batch) ->
616639
Encoded = kpro_batch:encode(2, Batch, no_compression),
617640
iolist_size(Encoded).
618641

619-
create_topic(Topic, Partitions, ReplicationFactor, MaxMessageBytes) ->
620-
Cmd = create_topic_cmd(Topic, Partitions, ReplicationFactor, MaxMessageBytes),
642+
create_topic(Topic, Partitions, ReplicationFactor, MaxMessageBytes, SegmentBytes) ->
643+
Cmd = create_topic_cmd(Topic, Partitions, ReplicationFactor, MaxMessageBytes, SegmentBytes),
621644
Result = os:cmd(Cmd),
622645
Expected = "Created topic " ++ Topic ++ ".\n",
623646
?assertEqual(Expected, Result),
@@ -631,7 +654,7 @@ delete_topic(Topic) ->
631654
_ -> throw(Result)
632655
end.
633656

634-
create_topic_cmd(Topic, Partitions, ReplicationFactor, MaxMessageBytes) ->
657+
create_topic_cmd(Topic, Partitions, ReplicationFactor, MaxMessageBytes, SegmentBytes) ->
635658
"docker exec wolff-kafka-1 /opt/kafka/bin/kafka-topics.sh" ++
636659
" --zookeeper zookeeper:2181" ++
637660
" --create" ++
@@ -643,6 +666,12 @@ create_topic_cmd(Topic, Partitions, ReplicationFactor, MaxMessageBytes) ->
643666
" --config max.message.bytes=" ++ integer_to_list(MaxMessageBytes);
644667
false ->
645668
""
669+
end ++
670+
case is_integer(SegmentBytes) of
671+
true ->
672+
" --config segment.bytes=" ++ integer_to_list(SegmentBytes);
673+
false ->
674+
""
646675
end.
647676

648677
delete_topic_cmd(Topic) ->

0 commit comments

Comments
 (0)