sink

package
v0.7.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BuildPostgresWriteQuery added in v0.5.0

func BuildPostgresWriteQuery(table string, columns []string, rowCount int, mode PostgresWriteMode, conflictColumns, updateColumns []string) string

BuildPostgresWriteQuery constructs the SQL used by PostgresSink. It is exported for tests and SQL inspection; callers should still use NewPostgresSink for actual writes.

func RecordToKafka

func RecordToKafka(r types.Record) kafka.Message

RecordToKafka converts a mailer.Record to a kafka.Message. Exported for backwards compatibility and testing. Does not run any configured serializer — use the sink's internal recordToKafka for that.

Types

type AcksLevel

type AcksLevel int

AcksLevel controls how many broker acknowledgements the Kafka sink waits for before considering a write successful.

const (
	// AcksNone does not wait for any acknowledgement (fire-and-forget).
	AcksNone AcksLevel = 0
	// AcksLeader waits for the leader broker to acknowledge the write.
	AcksLeader AcksLevel = 1
	// AcksAll waits for all in-sync replicas to acknowledge the write.
	AcksAll AcksLevel = -1
)

func (AcksLevel) Display added in v0.3.0

func (a AcksLevel) Display() string

Display returns a human-readable acks description for the dashboard.

type BlackholeSink

type BlackholeSink struct {
	// contains filtered or unexported fields
}

BlackholeSink discards all records. Used for benchmarking pipeline throughput without the overhead of writing to an external system.

func NewBlackholeSink

func NewBlackholeSink() *BlackholeSink

NewBlackholeSink creates a sink that silently discards all records.

func (*BlackholeSink) Count

func (s *BlackholeSink) Count() int64

Count returns the total number of records consumed.

func (*BlackholeSink) Write

func (s *BlackholeSink) Write(ctx context.Context, in <-chan types.Record) error

Write drains the input channel, counting records but discarding them.

type CheckpointedSink added in v0.4.0

type CheckpointedSink interface {
	Sink

	// SetOnPrepared registers the coordinator callback invoked when a
	// barrier's output is fully staged (or staging failed).
	SetOnPrepared(fn func(id string, err error))

	// Commit finalizes the transaction for checkpoint id and unblocks Write.
	Commit(ctx context.Context, id string) error

	// Abort discards the transaction for checkpoint id and unblocks Write.
	Abort(ctx context.Context, id string) error

	// WasCommitted reports whether checkpoint id's transaction
	// committed before a crash.
	WasCommitted(ctx context.Context, id string) (bool, error)
}

CheckpointedSink is a Sink that participates in coordinated exactly-once checkpoints. Output between two checkpoint barriers is staged (e.g. in a Kafka transaction) and becomes visible only when the coordinator calls Commit.

Contract:

  • Write sees checkpoint barriers in-band, after every pre-barrier record. On a barrier the sink must flush all staged output for the ending interval into the open transaction (plus a transaction marker identifying the checkpoint), invoke the OnPrepared callback, and BLOCK further writes until the coordinator calls Commit or Abort for that checkpoint.
  • Commit makes the interval's output visible and opens the next transaction. Abort discards it.
  • WasCommitted reports, after a restart, whether the transaction for a checkpoint ID actually committed (e.g. by probing the transaction marker under read-committed isolation). It resolves the prepared-but-unconfirmed recovery case.

type DLQ added in v0.4.0

type DLQ interface {
	Write(ctx context.Context, record types.Record) error
}

DLQ is a sink that receives records that could not be written to the primary sink after all retries. It is used together with FailurePolicyDLQ.

Common DLQ implementations: a separate Kafka topic, a file on disk, or a Postgres error table.

type Describable

type Describable interface {
	Describe() SinkInfo
}

Describable is an optional interface that Sinks can implement to expose metadata for the dashboard.

type FailurePolicy added in v0.4.0

type FailurePolicy int

FailurePolicy determines what happens when a sink write fails after all retry attempts have been exhausted.

const (
	// FailurePolicyDrop silently discards the record.
	FailurePolicyDrop FailurePolicy = iota

	// FailurePolicyDLQ forwards the record to a dead-letter-queue sink.
	FailurePolicyDLQ

	// FailurePolicyFail returns an error and stops the pipeline.
	FailurePolicyFail
)

type JSONSerializer

type JSONSerializer struct{}

JSONSerializer marshals Record.Parsed (if set) or Record.Value to JSON. When Parsed is nil and Value is already JSON bytes, this effectively passes them through (re-encoded, which is a no-op for valid JSON).

func NewJSONSerializer

func NewJSONSerializer() *JSONSerializer

NewJSONSerializer creates a JSON serializer.

func (*JSONSerializer) Serialize

func (s *JSONSerializer) Serialize(r types.Record) ([]byte, error)

type KafkaSink

type KafkaSink struct {
	// contains filtered or unexported fields
}

KafkaSink writes records to a single Kafka topic. It implements the Sink interface for use in mailer pipelines.

Configure a KafkaSink with functional options via NewKafkaSink:

sink := sink.NewKafkaSink(
    sink.KafkaSinkBrokers("localhost:9092"),
    sink.KafkaSinkTopic("order-summary"),
    sink.KafkaSinkBatchSize(200),
    sink.KafkaSinkRequiredAcks(sink.AcksAll),
    sink.KafkaSinkSASL(auth.SASLConfig{Mechanism: auth.SASLPlain, Username: "u", Password: "p"}),
    sink.KafkaSinkSerialize(sink.NewJSONSerializer()),
)

Records are written in batches for efficiency. On context cancellation, the sink drains remaining records for up to 5 seconds before flushing.

mailer.Record fields are mapped to kafka.Message as follows:

  • Key -> Message.Key
  • Value -> Message.Value (or serializer output if configured)
  • Timestamp -> Message.Time
  • Headers -> Message.Headers

func NewKafkaSink

func NewKafkaSink(opts ...KafkaSinkOption) *KafkaSink

NewKafkaSink creates a Sink that writes to a Kafka topic. Brokers and Topic are required; if missing, NewKafkaSink panics.

func (*KafkaSink) Describe

func (k *KafkaSink) Describe() SinkInfo

Describe returns metadata about this Kafka sink for the dashboard.

func (*KafkaSink) Write

func (k *KafkaSink) Write(ctx context.Context, in <-chan types.Record) error

Write reads records from the input channel and writes them to Kafka. It batches records for efficiency. On write failure, the batch is retried up to cfg.maxRetries times with exponential backoff. After all retries are exhausted, the failure policy is applied to each record in the batch.

On context cancellation, the sink drains remaining records for up to shutdownTimeout before performing a final flush.

type KafkaSinkOption

type KafkaSinkOption func(*kafkaSinkConfig)

KafkaSinkOption configures a KafkaSink. Pass one or more to NewKafkaSink. Brokers and Topic are required.

func KafkaSinkAsync

func KafkaSinkAsync() KafkaSinkOption

KafkaSinkAsync enables asynchronous writes — WriteMessages returns immediately without waiting for acknowledgement. Improves throughput but offers no durability guarantee on failure.

func KafkaSinkBatchSize

func KafkaSinkBatchSize(n int) KafkaSinkOption

KafkaSinkBatchSize sets the max messages per batch (default 100). Larger batches improve throughput at the cost of latency.

func KafkaSinkBatchTimeout

func KafkaSinkBatchTimeout(d time.Duration) KafkaSinkOption

KafkaSinkBatchTimeout sets the max wait before flushing a partial batch (default 1s). This bounds latency even when the batch is not full.

func KafkaSinkBrokers

func KafkaSinkBrokers(brokers ...string) KafkaSinkOption

KafkaSinkBrokers sets the Kafka bootstrap brokers. Required. Example: KafkaSinkBrokers("localhost:9092").

func KafkaSinkDLQ added in v0.4.0

func KafkaSinkDLQ(dlq DLQ) KafkaSinkOption

KafkaSinkDLQ sets the dead-letter-queue for failed records. Only used when KafkaSinkFailurePolicy is set to FailurePolicyDLQ.

func KafkaSinkFailurePolicy added in v0.4.0

func KafkaSinkFailurePolicy(p FailurePolicy) KafkaSinkOption

KafkaSinkFailurePolicy sets what happens when a record fails after all retries. Default is FailurePolicyDrop (record is silently discarded).

func KafkaSinkMaxRetries added in v0.4.0

func KafkaSinkMaxRetries(n int) KafkaSinkOption

KafkaSinkMaxRetries sets the number of times a failed write is retried before the failure policy is applied (default 3).

func KafkaSinkRequiredAcks

func KafkaSinkRequiredAcks(level AcksLevel) KafkaSinkOption

KafkaSinkRequiredAcks sets the acknowledgement level (default AcksLeader).

func KafkaSinkSASL

func KafkaSinkSASL(cfg auth.SASLConfig) KafkaSinkOption

KafkaSinkSASL enables SASL authentication on the Kafka connection.

func KafkaSinkSerialize

func KafkaSinkSerialize(s Serializer) KafkaSinkOption

KafkaSinkSerialize sets a Serializer that runs on every record before it is written to Kafka. If Record.Parsed is set, the serializer receives it; otherwise it receives Record.Value. The serializer's output replaces Record.Value in the Kafka message.

func KafkaSinkTLS

func KafkaSinkTLS(cfg auth.TLSConfig) KafkaSinkOption

KafkaSinkTLS enables TLS on the Kafka connection.

func KafkaSinkTopic

func KafkaSinkTopic(topic string) KafkaSinkOption

KafkaSinkTopic sets the destination topic. Required.

type PostgresSink

type PostgresSink struct {
	// contains filtered or unexported fields
}

PostgresSink writes records to a Postgres database in batches. It implements the Sink interface for use in mailer pipelines.

Configure a PostgresSink with functional options via NewPostgresSink:

sink := sink.NewPostgresSink(
    sink.PostgresDSN("postgres://user:pass@localhost:5432/dbname?sslmode=disable"),
    sink.PostgresMapper(func(r types.Record) (string, []string, []any) {
        o := r.Parsed.(*Order)
        return "orders",
            []string{"order_id", "customer", "amount"},
            []any{o.OrderID, o.Customer, o.Amount}
    }),
    sink.PostgresBatchSize(500),
)

Records are accumulated and inserted in batches using multi-value INSERT statements for efficiency. On context cancellation, the sink drains remaining records for up to 5 seconds before flushing the final batch.

func NewPostgresSink

func NewPostgresSink(opts ...PostgresSinkOption) *PostgresSink

NewPostgresSink creates a Sink that writes to Postgres. DSN and a RecordMapper are required; if missing, NewPostgresSink panics.

The connection pool is created immediately. If the database is unreachable, NewPostgresSink panics to fail fast at construction time.

func (*PostgresSink) Describe

func (p *PostgresSink) Describe() SinkInfo

Describe returns metadata about this Postgres sink for the dashboard.

func (*PostgresSink) Write

func (p *PostgresSink) Write(ctx context.Context, in <-chan types.Record) error

Write reads records from the input channel and writes them to Postgres in batches. On context cancellation, the sink drains remaining records for up to shutdownTimeout before flushing.

type PostgresSinkOption

type PostgresSinkOption func(*postgresSinkConfig)

PostgresSinkOption configures a PostgresSink. Pass one or more to NewPostgresSink. DSN and a RecordMapper are required.

func PostgresBatchSize

func PostgresBatchSize(n int) PostgresSinkOption

PostgresBatchSize sets the max records per INSERT batch (default 100). Larger batches reduce round-trips but increase memory usage.

func PostgresConflictColumns added in v0.5.0

func PostgresConflictColumns(cols ...string) PostgresSinkOption

PostgresConflictColumns sets the ON CONFLICT target for upserts.

func PostgresDLQ added in v0.4.0

func PostgresDLQ(dlq DLQ) PostgresSinkOption

PostgresDLQ sets the dead-letter-queue for failed records. Only used when PostgresFailurePolicy is set to FailurePolicyDLQ.

func PostgresDSN

func PostgresDSN(dsn string) PostgresSinkOption

PostgresDSN sets the Postgres connection string. Required. Example: "postgres://user:pass@localhost:5432/dbname?sslmode=disable".

func PostgresFailurePolicy added in v0.4.0

func PostgresFailurePolicy(p FailurePolicy) PostgresSinkOption

PostgresFailurePolicy sets what happens when a row fails after all retries. Default is FailurePolicyDrop (row is silently discarded).

func PostgresFlushInterval

func PostgresFlushInterval(d time.Duration) PostgresSinkOption

PostgresFlushInterval sets the max wait before flushing a partial batch (default 5s). Bounds latency even when the batch is not full.

func PostgresMapper

func PostgresMapper(m RecordMapper) PostgresSinkOption

PostgresMapper sets the function that maps each Record to a table, column list, and value list. Required.

func PostgresMaxRetries

func PostgresMaxRetries(n int) PostgresSinkOption

PostgresMaxRetries sets the number of times a failed batch insert is retried before giving up (default 3).

func PostgresMode added in v0.5.0

func PostgresMode(mode PostgresWriteMode) PostgresSinkOption

PostgresMode sets whether writes use plain INSERT or INSERT ... ON CONFLICT DO UPDATE. Default is PostgresInsert.

func PostgresUpdateColumns added in v0.5.0

func PostgresUpdateColumns(cols ...string) PostgresSinkOption

PostgresUpdateColumns sets the columns updated from EXCLUDED values on conflict. If omitted in upsert mode, all non-conflict inserted columns are updated.

type PostgresWriteMode added in v0.5.0

type PostgresWriteMode string

PostgresWriteMode selects how rows are written.

const (
	PostgresInsert PostgresWriteMode = "insert"
	PostgresUpsert PostgresWriteMode = "upsert"
)

type RecordMapper

type RecordMapper func(r types.Record) (table string, columns []string, values []any)

RecordMapper extracts the table name, column names, and values from a Record. The returned columns and values must be the same length and in the same order.

This is the core mapping function for the Postgres sink — it gives the user full control over how each Record maps to a database row.

Example:

mapper := func(r types.Record) (string, []string, []any) {
    o := r.Parsed.(*Order)
    return "orders",
        []string{"order_id", "customer", "amount"},
        []any{o.OrderID, o.Customer, o.Amount}
}

type Serializer

type Serializer interface {
	Serialize(r types.Record) ([]byte, error)
}

Serializer converts a Record into bytes for writing to an external system. Implementations are plugged into a KafkaSink via the KafkaSinkSerialize option.

If Record.Parsed is non-nil, serializers typically serialize the typed payload. Otherwise they fall back to Record.Value (the raw bytes). The returned bytes become the Kafka message value.

type SerializerFunc

type SerializerFunc func(r types.Record) ([]byte, error)

SerializerFunc lets a plain function satisfy the Serializer interface.

func (SerializerFunc) Serialize

func (f SerializerFunc) Serialize(r types.Record) ([]byte, error)

type Sink

type Sink interface {
	Write(ctx context.Context, in <-chan types.Record) error
}

Sink is where processed data leaves the pipeline. A Sink reads records from the input channel until it's closed or the context is cancelled.

type SinkInfo

type SinkInfo struct {
	Type  string            `json:"type"`
	Props map[string]string `json:"props"`
}

SinkInfo holds display metadata about a sink.

type StdoutSink

type StdoutSink struct{}

StdoutSink prints each record to stdout in a human-readable format. Useful for debugging and examples.

func NewStdoutSink

func NewStdoutSink() *StdoutSink

NewStdoutSink creates a sink that prints records to stdout.

func (*StdoutSink) Describe

func (s *StdoutSink) Describe() SinkInfo

Describe returns metadata for the dashboard.

func (*StdoutSink) Write

func (s *StdoutSink) Write(ctx context.Context, in <-chan types.Record) error

Write reads records from the input channel and prints each one to stdout.

type TxnKafkaOption added in v0.4.0

type TxnKafkaOption func(*txnKafkaConfig)

TxnKafkaOption configures a TxnKafkaSink.

func TxnKafkaBrokers added in v0.4.0

func TxnKafkaBrokers(brokers ...string) TxnKafkaOption

TxnKafkaBrokers sets the Kafka bootstrap brokers.

func TxnKafkaMarkerTopic added in v0.4.0

func TxnKafkaMarkerTopic(topic string) TxnKafkaOption

TxnKafkaMarkerTopic overrides the transaction-marker topic (default "<topic>.checkpoints"). Should be compacted; deleting it breaks crash recovery of prepared checkpoints.

func TxnKafkaSASL added in v0.5.0

func TxnKafkaSASL(cfg auth.SASLConfig) TxnKafkaOption

TxnKafkaSASL enables SASL authentication (PLAIN, SCRAM-SHA-256, or SCRAM-SHA-512) for hosted/secured clusters. Applies to both the transactional producer and the recovery marker-probe consumer.

func TxnKafkaSerialize added in v0.4.0

func TxnKafkaSerialize(s Serializer) TxnKafkaOption

TxnKafkaSerialize sets a serializer applied to record values.

func TxnKafkaTLS added in v0.5.0

func TxnKafkaTLS(cfg auth.TLSConfig) TxnKafkaOption

TxnKafkaTLS enables TLS. An empty auth.TLSConfig turns TLS on with system root CAs (the common hosted-Kafka case); CAFile/CertFile/ KeyFile configure private CAs and mutual TLS.

func TxnKafkaTopic added in v0.4.0

func TxnKafkaTopic(topic string) TxnKafkaOption

TxnKafkaTopic sets the output topic.

func TxnKafkaTransactionalID added in v0.4.0

func TxnKafkaTransactionalID(id string) TxnKafkaOption

TxnKafkaTransactionalID sets the Kafka transactional ID. Required. Must be stable across restarts of the same pipeline (fencing and marker attribution depend on it) and unique per pipeline instance.

type TxnKafkaSink added in v0.4.0

type TxnKafkaSink struct {
	// contains filtered or unexported fields
}

TxnKafkaSink writes records to Kafka inside transactions, one transaction per checkpoint interval, implementing CheckpointedSink for end-to-end exactly-once pipelines:

source offsets + operator state + sink output commit atomically.

Built on franz-go (segmentio/kafka-go has no transactional producer). Every checkpoint transaction also carries a marker record in the marker topic (default "<topic>.checkpoints"); after a crash, the marker's visibility under read_committed proves whether the transaction committed — this resolves prepared-but-unconfirmed checkpoints without Kafka transaction resumption.

IMPORTANT: downstream consumers of the output topic MUST use isolation.level=read_committed, or they will observe records from aborted transactions. Records become visible only when the checkpoint interval's transaction commits — the checkpoint interval is therefore also the output visibility latency.

Usage:

sk := sink.NewTxnKafkaSink(
    sink.TxnKafkaBrokers("localhost:9092"),
    sink.TxnKafkaTopic("order-summary"),
    sink.TxnKafkaTransactionalID("order-pipeline-1"),
)

The transactional ID must be unique per pipeline instance; a second instance with the same ID fences the first (Kafka zombie fencing).

func NewTxnKafkaSink added in v0.4.0

func NewTxnKafkaSink(opts ...TxnKafkaOption) *TxnKafkaSink

NewTxnKafkaSink creates a transactional Kafka sink. Panics on missing required configuration (broker connection errors surface from Write instead).

func (*TxnKafkaSink) Abort added in v0.4.0

func (s *TxnKafkaSink) Abort(ctx context.Context, id string) error

Abort implements CheckpointedSink: aborts the transaction for checkpoint id and unblocks Write. During recovery (sink not running) it is a no-op — producer fencing at the next InitProducerID aborts the dangling transaction broker-side.

func (*TxnKafkaSink) Commit added in v0.4.0

func (s *TxnKafkaSink) Commit(ctx context.Context, id string) error

Commit implements CheckpointedSink: commits the transaction for checkpoint id, opens the next one, and unblocks Write.

func (*TxnKafkaSink) Describe added in v0.4.0

func (s *TxnKafkaSink) Describe() SinkInfo

Describe returns dashboard metadata.

func (*TxnKafkaSink) SetOnPrepared added in v0.4.0

func (s *TxnKafkaSink) SetOnPrepared(fn func(id string, err error))

SetOnPrepared implements CheckpointedSink.

func (*TxnKafkaSink) TransactionalID added in v0.4.0

func (s *TxnKafkaSink) TransactionalID() string

TransactionalID reports the configured transactional ID (recorded in checkpoint files for diagnostics).

func (*TxnKafkaSink) WasCommitted added in v0.4.0

func (s *TxnKafkaSink) WasCommitted(ctx context.Context, id string) (bool, error)

WasCommitted implements CheckpointedSink: reads the marker topic under read_committed isolation and reports whether checkpoint id's marker is visible — i.e. whether its transaction committed.

func (*TxnKafkaSink) Write added in v0.4.0

func (s *TxnKafkaSink) Write(ctx context.Context, in <-chan types.Record) error

Write consumes records, producing them into the currently open transaction. On a checkpoint barrier it flushes, writes the marker, notifies the coordinator, and blocks until Commit or Abort.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL