github.com/wfusion/gofusion@v1.1.14/common/infra/watermill/pubsub/sql/offsets_adapter_mysql.go (about)

     1  package sql
     2  
     3  import (
     4  	"fmt"
     5  )
     6  
     7  // DefaultMySQLOffsetsAdapter is adapter for storing offsets for MySQL (or MariaDB) databases.
     8  //
     9  // DefaultMySQLOffsetsAdapter is designed to support multiple subscribers with exactly once delivery
    10  // and guaranteed order.
    11  //
    12  // We are using FOR UPDATE in NextOffsetQuery to lock consumer group in offsets table.
    13  //
    14  // When another consumer is trying to consume the same message, deadlock should occur in ConsumedMessageQuery.
    15  // After deadlock, consumer will consume next message.
    16  type DefaultMySQLOffsetsAdapter struct {
    17  	// GenerateMessagesOffsetsTableName may be used to override how the messages/offsets table name is generated.
    18  	GenerateMessagesOffsetsTableName func(topic string) string
    19  }
    20  
    21  func (a DefaultMySQLOffsetsAdapter) SchemaInitializingQueries(topic string) []string {
    22  	return []string{`
    23  		CREATE TABLE IF NOT EXISTS ` + a.MessagesOffsetsTable(topic) + ` (
    24  		consumer_group VARCHAR(255) NOT NULL,
    25  		offset_acked BIGINT,
    26  		offset_consumed BIGINT NOT NULL,
    27  		PRIMARY KEY(consumer_group)
    28  	)`}
    29  }
    30  
    31  func (a DefaultMySQLOffsetsAdapter) AckMessageQuery(topic string, row Row,
    32  	consumerGroup string) (string, []any) {
    33  	ackQuery := `INSERT INTO ` + a.MessagesOffsetsTable(topic) + " (offset_consumed, offset_acked, consumer_group) " +
    34  		"VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE " +
    35  		"offset_consumed=VALUES(offset_consumed), offset_acked=VALUES(offset_acked)"
    36  	return ackQuery, []any{row.Offset, row.Offset, consumerGroup}
    37  }
    38  
    39  func (a DefaultMySQLOffsetsAdapter) NextOffsetQuery(topic, consumerGroup string) (string, []any) {
    40  	return `SELECT COALESCE(
    41  				(SELECT offset_acked
    42  				 FROM ` + a.MessagesOffsetsTable(topic) + `
    43  				 WHERE consumer_group=? FOR UPDATE
    44  				), 0)`,
    45  		[]any{consumerGroup}
    46  }
    47  
    48  func (a DefaultMySQLOffsetsAdapter) MessagesOffsetsTable(topic string) string {
    49  	if a.GenerateMessagesOffsetsTableName != nil {
    50  		return a.GenerateMessagesOffsetsTableName(topic)
    51  	}
    52  	return fmt.Sprintf("`watermill_offsets_%s`", topic)
    53  }
    54  
    55  func (a DefaultMySQLOffsetsAdapter) ConsumedMessageQuery(topic string, row Row,
    56  	consumerGroup string, consumerULID []byte) (string, []any) {
    57  	// offset_consumed is not queried anywhere, it's used only to detect race conditions with NextOffsetQuery.
    58  	ackQuery := `INSERT INTO ` + a.MessagesOffsetsTable(topic) + ` (offset_consumed, consumer_group)
    59  		VALUES (?, ?) ON DUPLICATE KEY UPDATE offset_consumed=VALUES(offset_consumed)`
    60  	return ackQuery, []any{row.Offset, consumerGroup}
    61  }