Skip to content

feat: add chip-ingress batch config keys and tune defaults#2207

Merged
pavel-raykov merged 4 commits into
mainfrom
chip-ingress-batch-config
Jul 22, 2026
Merged

feat: add chip-ingress batch config keys and tune defaults#2207
pavel-raykov merged 4 commits into
mainfrom
chip-ingress-batch-config

Conversation

@pkcll

@pkcll pkcll commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Add 7 chip-ingress batch configuration keys to beholder.Config and wire them through the batch emitter and LOOP plugin env config.

This PR also updates the batch emitter defaults based on staging/prod capacity analysis and fixes the histogram bucket schema for chip_ingress.batch.request_size_*.

Default changes

Key Before After Rationale
ChipIngressBufferSize 1000 10000 Buffer headroom for transient send stalls
ChipIngressMaxBatchSize 500 1000 Burst-safety ceiling (batches are interval-bound, not size-bound)
ChipIngressSendInterval 100ms 500ms Reduce RPC volume on interval-bound batches
ChipIngressSendTimeout 3s 10s Match observed prod latency tail; preserve flush margin
ChipIngressDrainTimeout 10s 30s Preserve shutdown-flush margin over the larger SendTimeout
ChipIngressMaxConcurrentSends 10 10 Unchanged
ChipIngressMaxGRPCRequestSize 10 MiB 10 MiB Unchanged

Batches are interval-bound at steady state (p99 ~10 msgs/batch), so MaxBatchSize is a burst-safety ceiling rather than the active flush lever.

Changes

  • pkg/beholder/config.go: added ChipIngressMaxGRPCRequestSize + updated defaults
  • pkg/beholder/batch_emitter_service.go: wired WithMaxGRPCRequestSize
  • pkg/chipingress/batch/client.go: fixed histogram bucket boundaries for request_size_messages and request_size_bytes
  • pkg/loop/config.go: added CL_CHIP_INGRESS_* env vars, EnvConfig fields, serialization/parsing
  • pkg/loop/server.go: wired env fields into beholder.Config

Related PRs

Testing

  • go test ./pkg/loop/... ./pkg/beholder/... ./pkg/chipingress/...

Notes

  • DurableEmitter batch client intentionally unchanged.
  • Histogram bucket schema change causes transient mixed-schema skew until the fleet upgrades; this is expected and self-healing.
  • The previous Co-authored-by: Cursor attribution has been removed from the commit history.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

✅ API Diff Results - github.com/smartcontractkit/chainlink-common

✅ Compatible Changes (9)

pkg/beholder.Config (1)
  • ChipIngressMaxGRPCRequestSize — ➕ Added
pkg/beholder.writerClientConfig (1)
  • ChipIngressMaxGRPCRequestSize — ➕ Added
pkg/loop.EnvConfig (7)
  • ChipIngressBufferSize — ➕ Added

  • ChipIngressDrainTimeout — ➕ Added

  • ChipIngressMaxBatchSize — ➕ Added

  • ChipIngressMaxConcurrentSends — ➕ Added

  • ChipIngressMaxGRPCRequestSize — ➕ Added

  • ChipIngressSendInterval — ➕ Added

  • ChipIngressSendTimeout — ➕ Added


📄 View full apidiff report

Comment thread pkg/loop/config.go Fixed
@pkcll
pkcll force-pushed the chip-ingress-batch-config branch from 78f5646 to 1f54ac6 Compare June 29, 2026 22:11
@pkcll
pkcll force-pushed the chip-ingress-batch-config branch from 7c7d08a to 504389d Compare July 13, 2026 20:40
@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

📊 API Diff Results

No changes detected for module github.com/smartcontractkit/chainlink-common/pkg/chipingress

View full report

@pkcll
pkcll marked this pull request as ready for review July 21, 2026 05:41
@pkcll
pkcll requested review from a team as code owners July 21, 2026 05:41
Copilot AI review requested due to automatic review settings July 21, 2026 05:41
@pkcll
pkcll force-pushed the chip-ingress-batch-config branch from 9428215 to caf6101 Compare July 21, 2026 05:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds configurable chip-ingress batch emitter knobs end-to-end (beholder config ⇄ LOOP env config ⇄ server wiring), updates batch-emitter defaults based on capacity analysis, and corrects the chip_ingress.batch.request_size_* histogram bucket schemas.

Changes:

  • Extend beholder.Config with ChipIngressMaxGRPCRequestSize and update chip-ingress batch default values.
  • Add CL_CHIP_INGRESS_* env vars to LOOP EnvConfig, serialize/parse them, and wire them into beholder.Config in the LOOP server.
  • Adjust chip-ingress batch client histogram bucket boundaries for request sizes (messages + bytes).

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pkg/beholder/config.go Adds ChipIngressMaxGRPCRequestSize and updates chip-ingress batch emitter defaults.
pkg/beholder/batch_emitter_service.go Wires max gRPC request size into the batch client configuration.
pkg/beholder/batch_emitter_service_test.go Extends tests/bench config to include ChipIngressMaxGRPCRequestSize.
pkg/beholder/testdata/config-example.json Adds the new config key to the example JSON schema.
pkg/chipingress/batch/client.go Updates OTel histogram bucket boundaries for request size metrics.
pkg/loop/config.go Adds new CL_CHIP_INGRESS_* env vars, EnvConfig fields, serialization, and parsing helpers.
pkg/loop/config_test.go Updates env config tests to cover new chip-ingress env vars.
pkg/loop/server.go Wires new LOOP env config fields into beholder.Config.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/loop/config.go Outdated
Comment thread pkg/beholder/batch_emitter_service.go Outdated
Comment on lines +66 to +69
maxGRPCRequestSize := int(cfg.ChipIngressMaxGRPCRequestSize)
if maxGRPCRequestSize == 0 {
maxGRPCRequestSize = int(defaults.ChipIngressMaxGRPCRequestSize)
}
pkcll added a commit that referenced this pull request Jul 21, 2026
Bump github.com/smartcontractkit/chainlink-common/pkg/chipingress in the
main chainlink-common go.mod to the squashed chip-ingress-batch-config
commit (caf6101) so the main module resolves the updated chipingress
package that contains the chip_ingress.batch.request_size_* histogram
bucket changes.

This is a follow-up to #2207 and must merge after it.
Copilot AI review requested due to automatic review settings July 21, 2026 06:30
@pkcll pkcll changed the title feat: add chip-ingress batch config keys chip-ingress batch config and Beholder resource attributes Jul 21, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 15 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

pkg/loop/config.go:744

  • getUint parses into a uint64 and then casts to uint without bounds checking. On 32-bit platforms (or if a very large value is provided), this can overflow and wrap silently, leading to incorrect configuration values.
func getUint(envKey string) (uint, error) {
	s := os.Getenv(envKey)
	if s == "" {
		return 0, nil
	}
	u, err := strconv.ParseUint(s, 10, 64)
	if err != nil {
		return 0, err
	}
	return uint(u), nil
}

pkg/beholder/batch_emitter_service.go:70

  • ChipIngressMaxGRPCRequestSize is converted from uint to int without range checks. If a user configures a value larger than max int, the cast can overflow and become negative/incorrect, which would be passed into batch.WithMaxGRPCRequestSize.
	maxGRPCRequestSize := int(cfg.ChipIngressMaxGRPCRequestSize)
	if maxGRPCRequestSize == 0 {
		maxGRPCRequestSize = int(defaults.ChipIngressMaxGRPCRequestSize)
	}

Comment on lines 20 to 26
type ChipIngressBatchEmitterService struct {
services.Service
eng *services.Engine

batchClient *batch.Client
batchClient *batch.Client
resourceAttrs map[string]string

Copilot AI review requested due to automatic review settings July 21, 2026 06:37
Copilot AI review requested due to automatic review settings July 21, 2026 21:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

pkg/loop/config.go:613

  • ChipIngressMaxConcurrentSends is parsed as an int but negative values are currently accepted. A negative value will panic at runtime when passed into batch.WithMaxConcurrentSends (it uses make(chan struct{}, maxConcurrentSends)). Please reject negative values during env parsing (consistent with the existing negative checks for other chip-ingress keys).
		e.ChipIngressMaxConcurrentSends, err = getInt(envChipIngressMaxConcurrentSends)
		if err != nil {
			return fmt.Errorf("failed to parse %s: %w", envChipIngressMaxConcurrentSends, err)
		}
		e.ChipIngressSendInterval, err = getDuration(envChipIngressSendInterval)
		if err != nil {
			return fmt.Errorf("failed to parse %s: %w", envChipIngressSendInterval, err)
		}

pkg/loop/config.go:747

  • getUint parses into a uint64 and then casts to uint without an overflow check. On 32-bit builds this can silently wrap, and even on 64-bit it can allow values that later overflow when converted to int (e.g., in beholder batch emitter config). Prefer parsing with a bitSize that matches the target word size so overflow becomes a parse error.
func getUint(envKey string) (uint, error) {
	s := os.Getenv(envKey)
	if s == "" {
		return 0, nil
	}
	u, err := strconv.ParseUint(s, 10, 64)
	if err != nil {
		return 0, err
	}
	return uint(u), nil
}

Copilot AI review requested due to automatic review settings July 21, 2026 21:52
@pkcll
pkcll force-pushed the chip-ingress-batch-config branch from 0a4817d to 733491d Compare July 21, 2026 21:52
@pkcll
pkcll requested a review from a team as a code owner July 21, 2026 21:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 24 changed files in this pull request and generated 7 comments.

Comments suppressed due to low confidence (1)

pkg/loop/config.go:746

  • getUint parses into a uint64 and then casts to uint without overflow checking. On 32-bit builds this can silently wrap, producing a much smaller value than configured. Add an explicit bounds check before casting.
	u, err := strconv.ParseUint(s, 10, 64)
	if err != nil {
		return 0, err
	}
	return uint(u), nil

Comment thread pkg/beholder/client.go Outdated
Comment on lines 366 to 370
// Add custom resource attributes
resource, err = sdkresource.Merge(
resource,
sdkresource.NewSchemaless(cfg.ResourceAttributes...),
resource,
)
Comment on lines 83 to 86
batch.WithMaxConcurrentSends(maxConcurrentSends),
batch.WithMaxGRPCRequestSize(maxGRPCRequestSize),
batch.WithEventClone(false),
batch.WithClientName(batch.ClientNameBeholder),
)
Comment on lines 114 to 117
chipingressbatch.WithMessageBuffer(defaultInt(cfg.MessageBufferSize, 10_000)),
chipingressbatch.WithMaxPublishTimeout(defaultDuration(cfg.MaxPublishTimeout, 5*time.Second)),
chipingressbatch.WithShutdownTimeout(defaultDuration(cfg.ShutdownTimeout, 30*time.Second)),
chipingressbatch.WithClientName(chipingressbatch.ClientNameDurableEmitter),
)
Comment thread pkg/services/service.go
Comment on lines 265 to 268
if err := ms.Start(ctx, sub); err != nil {
s.eng.Errorw("Failed to start sub-service", "name", sub.Name(), "error", err)
return fmt.Errorf("failed to start sub-service %s of %s: %w", sub.Name(), s.cfg.Name, err)
s.eng.Errorw("Failed to start sub-service", "error", err)
return fmt.Errorf("failed to start sub-service of %s: %w", s.cfg.Name, err)
}
Comment on lines 16 to 20
const (
beholderDomain = "node-platform"
beholderProtobufEntity = "common.v1.HealthInfo"
beholderProtobufEntity = "common.v1.NodeHealthInfo"
beholderProtobufDataSchema = "/node-platform/common/v1"
)
Comment thread go.mod
Comment on lines 8 to 11
github.com/andybalholm/brotli v1.1.1
github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c
github.com/bytecodealliance/wasmtime-go/v47 v47.0.0
github.com/bytecodealliance/wasmtime-go/v28 v28.0.0
github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.16.1
Comment thread go.mod
Comment on lines 44 to 47
github.com/shopspring/decimal v1.4.0
github.com/smartcontractkit/chain-selectors v1.0.100
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260716165322-7f2edff6e954
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260626151909-052e55e62e62
github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4
pkcll added 2 commits July 22, 2026 00:33
Expose 7 chip-ingress batch emitter configuration keys in beholder.Config
and wire them through the batch emitter and LOOP plugin env config.

Default changes:
- ChipIngressBufferSize: 1000 -> 10000
- ChipIngressMaxBatchSize: 500 -> 1000
- ChipIngressSendInterval: 100ms -> 500ms
- ChipIngressSendTimeout: 3s -> 10s
- ChipIngressDrainTimeout: 10s -> 30s

Also fix the chip_ingress.batch.request_size_* histogram buckets to use
explicit low-cardinality boundaries, and remove the previous
Co-authored-by Cursor attribution from the commit history.
@pkcll
pkcll force-pushed the chip-ingress-batch-config branch from 733491d to d44059c Compare July 22, 2026 04:35
Copilot AI review requested due to automatic review settings July 22, 2026 04:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

pkg/loop/config.go:609

  • CL_CHIP_INGRESS_MAX_CONCURRENT_SENDS is parsed with getInt but isn't validated for negative values. A negative value will panic at runtime when batch.WithMaxConcurrentSends does make(chan struct{}, maxConcurrentSends) with a negative capacity. Add a non-negative validation similar to ChipIngressMaxGRPCRequestSize.
		e.ChipIngressMaxConcurrentSends, err = getInt(envChipIngressMaxConcurrentSends)
		if err != nil {
			return fmt.Errorf("failed to parse %s: %w", envChipIngressMaxConcurrentSends, err)
		}

pkg/loop/config.go:746

  • getUint parses into a uint64 then casts to uint without checking for overflow. On 32-bit platforms (or with very large env values), this can wrap/truncate and potentially produce an invalid buffer/batch size downstream. Consider adding a platform-size overflow guard before converting.
	u, err := strconv.ParseUint(s, 10, 64)
	if err != nil {
		return 0, err
	}
	return uint(u), nil

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 22, 2026 10:55
@pavel-raykov
pavel-raykov enabled auto-merge July 22, 2026 11:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

pkg/loop/config.go:609

  • CL_CHIP_INGRESS_MAX_CONCURRENT_SENDS is parsed via getInt but negative values are currently accepted. A negative value will later panic when used to size the semaphore channel in batch.WithMaxConcurrentSends. Please reject negative values during env parsing (0 can remain the sentinel for "use defaults").
			return fmt.Errorf("failed to parse %s: %w", envChipIngressDurableEmitterEnabled, err)
		}
		e.ChipIngressBufferSize, err = getUint(envChipIngressBufferSize)
		if err != nil {

pkg/loop/config.go:746

  • getUint currently allows values up to the full uint range (e.g., 2^64-1 on 64-bit), but downstream these chip-ingress sizes are cast to int when wiring the batch client. Values larger than MaxInt will overflow to negative and can cause panics when used as channel capacities. Consider bounding parsed values to MaxInt and returning an error when exceeded.
	s := os.Getenv(envKey)
	if s == "" {
		return 0, nil
	}
	return time.ParseDuration(s)

Comment on lines +66 to +69
maxGRPCRequestSize := cfg.ChipIngressMaxGRPCRequestSize
if maxGRPCRequestSize == 0 {
maxGRPCRequestSize = defaults.ChipIngressMaxGRPCRequestSize
}
Copilot AI review requested due to automatic review settings July 22, 2026 11:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

pkg/loop/config.go:619

  • CL_CHIP_INGRESS_MAX_CONCURRENT_SENDS can currently be set to a negative value, which will flow into batch.WithMaxConcurrentSends and panic when it creates a channel with negative capacity. Consider rejecting negative values during env parsing (similar to the existing ChipIngressMaxGRPCRequestSize validation).
		e.ChipIngressMaxConcurrentSends, err = getInt(envChipIngressMaxConcurrentSends)
		if err != nil {
			return fmt.Errorf("failed to parse %s: %w", envChipIngressMaxConcurrentSends, err)
		}

Comment thread pkg/beholder/config.go
Comment on lines 58 to +66
// Chip Ingress Batch Emitter
ChipIngressBatchEmitterEnabled bool // When true, use batch emitter; when false (default), use legacy per-event emitter
ChipIngressBufferSize uint // Message buffer size (default 1000)
ChipIngressMaxBatchSize uint // Max events per PublishBatch call (default 500)
ChipIngressSendInterval time.Duration // Flush interval (default 100ms)
ChipIngressSendTimeout time.Duration // Timeout per PublishBatch call (default 3s)
ChipIngressDrainTimeout time.Duration // Max time to flush remaining events on shutdown (default 10s)
ChipIngressBufferSize uint // Message buffer size (default 10000)
ChipIngressMaxBatchSize uint // Max events per PublishBatch call (default 1000)
ChipIngressSendInterval time.Duration // Flush interval (default 500ms)
ChipIngressSendTimeout time.Duration // Timeout per PublishBatch call (default 10s)
ChipIngressDrainTimeout time.Duration // Max time to flush remaining events on shutdown (default 30s)
ChipIngressMaxConcurrentSends int // Max concurrent PublishBatch calls (default 10)
ChipIngressMaxGRPCRequestSize int // Max serialized PublishBatch request size in bytes (default 10 MiB)
@pavel-raykov
pavel-raykov added this pull request to the merge queue Jul 22, 2026
Merged via the queue into main with commit c1a1e0e Jul 22, 2026
33 of 34 checks passed
@pavel-raykov
pavel-raykov deleted the chip-ingress-batch-config branch July 22, 2026 12:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants