55 "database/sql"
66 "errors"
77 "fmt"
8+ "strings"
89 "time"
910
1011 "github.com/scality/log-courier/pkg/clickhouse"
@@ -29,6 +30,7 @@ import (
2930// OffsetManagerInterface defines the interface for offset management
3031type OffsetManagerInterface interface {
3132 CommitOffset (ctx context.Context , bucket string , raftSessionID uint16 , offset Offset ) error
33+ CommitOffsetsBatch (ctx context.Context , requests []OffsetCommitRequest ) error
3234 GetOffset (ctx context.Context , bucket string , raftSessionID uint16 ) (Offset , error )
3335}
3436
@@ -39,6 +41,13 @@ type Offset struct {
3941 ReqID string
4042}
4143
44+ // OffsetCommitRequest holds a single offset commit request
45+ type OffsetCommitRequest struct {
46+ Offset Offset
47+ Bucket string
48+ RaftSessionID uint16
49+ }
50+
4251// OffsetManager manages offsets
4352type OffsetManager struct {
4453 client * clickhouse.Client
@@ -55,27 +64,52 @@ func NewOffsetManager(client *clickhouse.Client, database string) *OffsetManager
5564
5665// CommitOffset commits the processing offset for a bucket using composite key
5766func (om * OffsetManager ) CommitOffset (ctx context.Context , bucket string , raftSessionID uint16 , offset Offset ) error {
58- if bucket == "" {
59- return fmt .Errorf ("bucket name cannot be empty" )
60- }
61- if offset .InsertedAt .IsZero () {
62- return fmt .Errorf ("insertedAt timestamp cannot be zero" )
67+ return om .CommitOffsetsBatch (ctx , []OffsetCommitRequest {{
68+ Offset : offset ,
69+ Bucket : bucket ,
70+ RaftSessionID : raftSessionID ,
71+ }})
72+ }
73+
74+ // CommitOffsetsBatch commits multiple offsets in a single database operation
75+ func (om * OffsetManager ) CommitOffsetsBatch (ctx context.Context , requests []OffsetCommitRequest ) error {
76+ if len (requests ) == 0 {
77+ return nil
6378 }
64- if offset .Timestamp .IsZero () {
65- return fmt .Errorf ("timestamp cannot be zero" )
79+
80+ // Validate all requests first
81+ for i , req := range requests {
82+ if req .Bucket == "" {
83+ return fmt .Errorf ("request %d: bucket name cannot be empty" , i )
84+ }
85+ if req .Offset .InsertedAt .IsZero () {
86+ return fmt .Errorf ("request %d: insertedAt timestamp cannot be zero" , i )
87+ }
88+ if req .Offset .Timestamp .IsZero () {
89+ return fmt .Errorf ("request %d: timestamp cannot be zero" , i )
90+ }
91+ if req .Offset .ReqID == "" {
92+ return fmt .Errorf ("request %d: reqID cannot be empty" , i )
93+ }
6694 }
67- if offset .ReqID == "" {
68- return fmt .Errorf ("reqID cannot be empty" )
95+
96+ // Build query with multiple VALUES clauses for batch insert
97+ valuesClauses := make ([]string , len (requests ))
98+ args := make ([]interface {}, 0 , len (requests )* 5 )
99+
100+ for i , req := range requests {
101+ valuesClauses [i ] = "(?, ?, ?, ?, ?)"
102+ args = append (args , req .Bucket , req .RaftSessionID , req .Offset .InsertedAt , req .Offset .Timestamp , req .Offset .ReqID )
69103 }
70104
71105 query := fmt .Sprintf (`
72106 INSERT INTO %s.%s (bucketName, raftSessionID, lastProcessedInsertedAt, lastProcessedTimestamp, lastProcessedReqId)
73- VALUES (?, ?, ?, ?, ?)
74- ` , om .database , clickhouse .TableOffsetsFederated )
107+ VALUES %s
108+ ` , om .database , clickhouse .TableOffsetsFederated , strings . Join ( valuesClauses , ", " ) )
75109
76- err := om .client .Exec (ctx , query , bucket , raftSessionID , offset . InsertedAt , offset . Timestamp , offset . ReqID )
110+ err := om .client .Exec (ctx , query , args ... )
77111 if err != nil {
78- return fmt .Errorf ("failed to commit offset for bucket %s raftSessionID %d : %w" , bucket , raftSessionID , err )
112+ return fmt .Errorf ("failed to commit %d offsets : %w" , len ( requests ) , err )
79113 }
80114
81115 return nil
0 commit comments