Skip to content

Commit d2143f8

Browse files
alexluongclaude
andauthored
fix: add ClickHouse client and InsertMany timeouts (#700)
* chore: improve logmq logs Add message_id to the handler log entry, event_id, attempt_id, and tenant_id after parsing in the batch processor. This makes it possible to trace message redeliveries during pipeline stalls. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add ClickHouse client and InsertMany timeouts Add transport-level timeouts (DialTimeout: 10s, ReadTimeout: 30s) to the ClickHouse client to prevent TCP-level hangs like the Feb 10 incident where a hung write took 56 min to naturally fail via read: EOF. Also add a 30s application-level context timeout to the InsertMany call in the batch processor, bounding how long a single flush can block regardless of transport behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: log insert_duration_ms in batch processor Add insert duration timing to both success and error log lines in processBatch, making it easier to spot slow ClickHouse inserts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6d5f0a4 commit d2143f8

3 files changed

Lines changed: 34 additions & 6 deletions

File tree

internal/clickhouse/clickhouse.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package clickhouse
22

33
import (
44
"crypto/tls"
5+
"time"
56

67
"github.com/ClickHouse/clickhouse-go/v2"
78
chdriver "github.com/ClickHouse/clickhouse-go/v2"
@@ -29,6 +30,8 @@ func New(config *ClickHouseConfig) (DB, error) {
2930
Username: config.Username,
3031
Password: config.Password,
3132
},
33+
DialTimeout: 10 * time.Second,
34+
ReadTimeout: 30 * time.Second,
3235

3336
// Debug: true,
3437
// Debugf: func(format string, v ...any) {

internal/logmq/batchprocessor.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,30 @@ func (bp *BatchProcessor) processBatch(_ string, msgs []*mqs.Message) {
9090
// Validate that both Event and Attempt are present.
9191
// The logstore requires both for data consistency.
9292
if entry.Event == nil || entry.Attempt == nil {
93-
logger.Error("invalid log entry: both event and attempt are required",
93+
fields := []zap.Field{
9494
zap.Bool("has_event", entry.Event != nil),
9595
zap.Bool("has_attempt", entry.Attempt != nil),
96-
zap.String("message_id", msg.LoggableID))
96+
zap.String("message_id", msg.LoggableID),
97+
}
98+
if entry.Event != nil {
99+
fields = append(fields, zap.String("event_id", entry.Event.ID))
100+
fields = append(fields, zap.String("tenant_id", entry.Event.TenantID))
101+
}
102+
if entry.Attempt != nil {
103+
fields = append(fields, zap.String("attempt_id", entry.Attempt.ID))
104+
fields = append(fields, zap.String("tenant_id", entry.Attempt.TenantID))
105+
}
106+
logger.Error("invalid log entry: both event and attempt are required", fields...)
97107
msg.Nack()
98108
continue
99109
}
100110

111+
logger.Info("added to batch",
112+
zap.String("message_id", msg.LoggableID),
113+
zap.String("event_id", entry.Event.ID),
114+
zap.String("attempt_id", entry.Attempt.ID),
115+
zap.String("tenant_id", entry.Event.TenantID))
116+
101117
entries = append(entries, entry)
102118
validMsgs = append(validMsgs, msg)
103119
}
@@ -107,17 +123,24 @@ func (bp *BatchProcessor) processBatch(_ string, msgs []*mqs.Message) {
107123
return
108124
}
109125

110-
if err := bp.logStore.InsertMany(bp.ctx, entries); err != nil {
126+
insertCtx, cancel := context.WithTimeout(bp.ctx, 30*time.Second)
127+
defer cancel()
128+
129+
insertStart := time.Now()
130+
if err := bp.logStore.InsertMany(insertCtx, entries); err != nil {
111131
logger.Error("failed to insert log entries",
112132
zap.Error(err),
113-
zap.Int("entry_count", len(entries)))
133+
zap.Int("entry_count", len(entries)),
134+
zap.Int64("insert_duration_ms", time.Since(insertStart).Milliseconds()))
114135
for _, msg := range validMsgs {
115136
msg.Nack()
116137
}
117138
return
118139
}
119140

120-
logger.Info("batch processed successfully", zap.Int("count", len(validMsgs)))
141+
logger.Info("batch processed successfully",
142+
zap.Int("count", len(validMsgs)),
143+
zap.Int64("insert_duration_ms", time.Since(insertStart).Milliseconds()))
121144

122145
for _, msg := range validMsgs {
123146
msg.Ack()

internal/logmq/messagehandler.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/hookdeck/outpost/internal/consumer"
77
"github.com/hookdeck/outpost/internal/logging"
88
"github.com/hookdeck/outpost/internal/mqs"
9+
"go.uber.org/zap"
910
)
1011

1112
// BatchAdder is the interface for adding messages to a batch processor.
@@ -29,7 +30,8 @@ func NewMessageHandler(logger *logging.Logger, batchAdder BatchAdder) consumer.M
2930

3031
func (h *messageHandler) Handle(ctx context.Context, msg *mqs.Message) error {
3132
logger := h.logger.Ctx(ctx)
32-
logger.Info("logmq handler")
33+
logger.Info("logmq handler",
34+
zap.String("message_id", msg.LoggableID))
3335
h.batchAdder.Add(ctx, msg)
3436
return nil
3537
}

0 commit comments

Comments
 (0)