Skip to content

Commit fc4a93b

Browse files
committed
fix(telemetry): ensure the failed counter is correct
Previously, the failed metric counter might be double-incremented for sent requests due to the attempt counter ws bumped only before re-sending. Now the attempt counter is bumped right after the failed metric counter is incremented.
1 parent f0adcae commit fc4a93b

1 file changed

Lines changed: 32 additions & 22 deletions

File tree

src/wolff_producer.erl

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -311,13 +311,13 @@ handle_info({msg, Conn, Rsp}, #{conn := Conn} = St0) ->
311311
St = maybe_send_to_kafka(St1),
312312
{noreply, St}
313313
catch
314-
throw : Reason ->
314+
throw : {kafka_error, Reason, St1} ->
315315
%% connection is not really down, but we need to
316316
%% stay down for a while and maybe restart sending on a new connection
317317
%% if Reason is not_leader_for_partition,
318318
%% wolff_client should expire old metadata while we are down
319319
%% and connect to the new leader when we request for a new connection
320-
St = mark_connection_down(St0, Reason),
320+
St = mark_connection_down(St1, Reason),
321321
{noreply, St}
322322
end;
323323
handle_info(?leader_connection(Conn), St) ->
@@ -423,13 +423,13 @@ use_defaults(Config, [{K, V} | Rest]) ->
423423
false -> use_defaults(Config#{K => V}, Rest)
424424
end.
425425

426-
remake_requests(#{q_items := Items, attempts := Attempts} = Sent, St, ?reconnect) ->
426+
remake_requests(#{q_items := Items} = Sent, St, ?reconnect) ->
427427
#kpro_req{ref = Ref} = Req = make_request(Items, St),
428-
{[Req], [Sent#{req_ref := Ref, attempts := Attempts + 1}]};
428+
{[Req], [Sent#{req_ref := Ref}]};
429429
remake_requests(#{q_items := Items} = Sent, St, ?message_too_large) ->
430430
Reqs = lists:map(fun(Item) -> make_request([Item], St) end, Items),
431431
#{attempts := Attempts, q_ack_ref := Q_AckRef} = Sent,
432-
{Reqs, make_sent_items_list(Items, Reqs, Attempts + 1, Q_AckRef)}.
432+
{Reqs, make_sent_items_list(Items, Reqs, Attempts, Q_AckRef)}.
433433

434434
%% only ack replayq when the last message is accepted by Kafka
435435
make_sent_items_list([LastItem], [#kpro_req{ref = Ref}], Attempts, Q_AckRef) ->
@@ -528,13 +528,13 @@ send_to_kafka(#{sent_reqs := SentReqs,
528528
pending_acks := NewPendingAcks
529529
},
530530
ok = request_async(Conn, Req),
531-
St2 = maybe_fake_kafka_ack(NoAck, Sent, St1),
531+
St2 = maybe_fake_kafka_ack(NoAck, St1),
532532
maybe_send_to_kafka(St2).
533533

534534
%% when require no acks do not add to sent_reqs and ack caller immediately
535-
maybe_fake_kafka_ack(_NoAck = true, Sent, St) ->
536-
do_handle_kafka_ack(?no_error, ?UNKNOWN_OFFSET, Sent, St);
537-
maybe_fake_kafka_ack(_NoAck, _Sent, St) -> St.
535+
maybe_fake_kafka_ack(_NoAck = true, St) ->
536+
do_handle_kafka_ack(?no_error, ?UNKNOWN_OFFSET, St);
537+
maybe_fake_kafka_ack(_NoAck, St) -> St.
538538

539539
is_send_ahead_allowed(#{config := #{max_send_ahead := Max},
540540
sent_reqs_count := SentCount}) ->
@@ -603,9 +603,9 @@ handle_kafka_ack(#kpro_rsp{api = produce,
603603
ErrorCode = kpro:find(error_code, PartitionRsp),
604604
BaseOffset = kpro:find(base_offset, PartitionRsp),
605605
case queue:peek(SentReqs) of
606-
{value, #{req_ref := Ref} = Sent} ->
606+
{value, #{req_ref := Ref1}} when Ref1 =:= Ref ->
607607
%% sent_reqs queue front matched the response reference
608-
do_handle_kafka_ack(ErrorCode, BaseOffset, Sent, St);
608+
do_handle_kafka_ack(ErrorCode, BaseOffset, St);
609609
_ ->
610610
%% stale response e.g. when inflight > 1, but we have to retry an older req
611611
St
@@ -614,12 +614,12 @@ handle_kafka_ack(#kpro_rsp{api = produce,
614614
note(Fmt, Args) ->
615615
lists:flatten(io_lib:format(Fmt, Args)).
616616

617-
-spec do_handle_kafka_ack(_, _, sent(), state()) -> state().
618-
do_handle_kafka_ack(?no_error, BaseOffset, _Sent, St) ->
617+
-spec do_handle_kafka_ack(_, _, state()) -> state().
618+
do_handle_kafka_ack(?no_error, BaseOffset, St) ->
619619
clear_sent_and_ack_callers(?no_error, BaseOffset, St);
620-
do_handle_kafka_ack(EC, _BaseOffset, Sent, St) when EC =:= ?message_too_large orelse EC =:= ?record_list_too_large ->
621-
#{topic := Topic, partition := Partition, config := Config} = St,
622-
#{q_items := Items} = Sent,
620+
do_handle_kafka_ack(EC, _BaseOffset, St) when EC =:= ?message_too_large orelse EC =:= ?record_list_too_large ->
621+
#{topic := Topic, partition := Partition, config := Config, sent_reqs := SentReqs} = St,
622+
{value, #{q_items := Items}} = queue:peek(SentReqs),
623623
#kpro_req{msg = IoData} = make_request(Items, St),
624624
Bytes = iolist_size(IoData),
625625
Hint = case EC of
@@ -649,11 +649,14 @@ do_handle_kafka_ack(EC, _BaseOffset, Sent, St) when EC =:= ?message_too_large or
649649
"Will use max_batch_bytes=~p to collect future batches. " ++ Hint,
650650
[Max, NewMax]),
651651
log_warn(Topic, Partition, EC, #{note => Note, calls_count => N, encode_bytes => Bytes}),
652+
%% We do not incement the attempt counter for too large batch
653+
%% St2 = increment_attempt_for_first_sent_req(St),
652654
resend_sent_reqs(St1, Reason)
653655
end;
654-
do_handle_kafka_ack(ErrorCode, _BaseOffset, Sent, St) ->
656+
do_handle_kafka_ack(ErrorCode, _BaseOffset, St) ->
655657
%% Other errors, such as not_leader_for_partition
656-
#{attempts := Attempts, q_items := Items} = Sent,
658+
#{sent_reqs := SentReqs} = St,
659+
{value, #{attempts := Attempts, q_items := Items}} = queue:peek(SentReqs),
657660
#{topic := Topic,
658661
partition := Partition,
659662
config := Config
@@ -664,7 +667,15 @@ do_handle_kafka_ack(ErrorCode, _BaseOffset, Sent, St) ->
664667
#{error_code => ErrorCode,
665668
batch_size => count_msgs(Items),
666669
attempts => Attempts}),
667-
erlang:throw(ErrorCode).
670+
St1 = increment_attempt(St),
671+
erlang:throw({kafka_error, ErrorCode, St1}).
672+
673+
%% Since we only handle Kafka ack for the first sent request in the sent queue
674+
%% we only bump the first itme with attempt counter
675+
increment_attempt(#{sent_reqs := SentReqs} = St) ->
676+
{{value, SentReq}, SentReqs1} = queue:out(SentReqs),
677+
#{attempts := Attempts} = SentReq,
678+
St#{sent_reqs := queue:in_r(SentReq#{attempts := Attempts + 1}, SentReqs1)}.
668679

669680
clear_sent_and_ack_callers(ErrorCode, BaseOffset, St) ->
670681
#{sent_reqs := SentReqs,
@@ -1020,11 +1031,10 @@ reply_error_for_all_reqs(St, Reason) ->
10201031
end,
10211032
Count = wolff_pendack:fold(PendingAcks, F, 0),
10221033
AlreadyInc = lists:foldl(
1023-
fun(#{attempts := 1}, Acc) ->
1024-
%% sent but not yet acked
1034+
fun(#{attempts := Attempts}, Acc) when Attempts > 1 ->
1035+
%% 'failed' counter is already incremented for this sent request
10251036
Acc + 1;
10261037
(_, Acc) ->
1027-
%% sent and acked with error, the failed count is already incremented
10281038
Acc
10291039
end, 0, queue:to_list(SentReqs)),
10301040
inc_sent_failed(Config, Count - AlreadyInc, 1).

0 commit comments

Comments
 (0)