apache/beam · error

can't apply an alignment period of less than a millisecond.

Error message

can't apply an alignment period of less than a millisecond. Got: %v

What it means

AlignedTo aligns an AfterProcessingTime trigger to multiples of a period; periods below one millisecond cannot be represented in the millisecond-quantized transform, so the method panics. The offset may be a zero time.Time{} and is optional.

Source

Thrown at sdks/go/pkg/beam/core/graph/window/trigger/trigger.go:171

// PlusDelay configures an AfterProcessingTime trigger to fire after a specified delay,
// no smaller than a millisecond.
func (t *AfterProcessingTimeTrigger) PlusDelay(delay time.Duration) *AfterProcessingTimeTrigger {
	if delay < time.Millisecond {
		panic(fmt.Errorf("can't apply processing delay of less than a millisecond. Got: %v", delay))
	}
	t.timestampTransforms = append(t.timestampTransforms, DelayTransform{Delay: int64(delay / time.Millisecond)})
	return t
}

// AlignedTo configures an AfterProcessingTime trigger to fire
// at the smallest multiple of period since the offset greater than the first element timestamp.
//
// * Period may not be smaller than a millisecond.
// * Offset may be a zero time (time.Time{}).
func (t *AfterProcessingTimeTrigger) AlignedTo(period time.Duration, offset time.Time) *AfterProcessingTimeTrigger {
	if period < time.Millisecond {
		panic(fmt.Errorf("can't apply an alignment period of less than a millisecond. Got: %v", period))
	}
	offsetMillis := int64(0)
	if !offset.IsZero() {
		// TODO: Change to call UnixMilli() once we move to only supporting a go version > 1.17.
		offsetMillis = offset.Unix()*1e3 + int64(offset.Nanosecond())/1e6
	}
	t.timestampTransforms = append(t.timestampTransforms, AlignToTransform{
		Period: int64(period / time.Millisecond),
		Offset: offsetMillis,
	})
	return t
}

// RepeatTrigger fires a sub-trigger repeatedly.
type RepeatTrigger struct {
	subtrigger Trigger
}

View on GitHub (pinned to 12126d8942)

Solutions

  1. Pass an alignment period of at least time.Millisecond.
  2. Clamp the period to time.Millisecond before calling AlignedTo.
  3. Validate the config value supplying the period (guard against 0).
  4. For finer alignment granularity, implement a custom trigger rather than using AlignedTo.

Example fix

// before
t := trigger.AfterProcessingTime().AlignedTo(cfg.Period, time.Time{}) // cfg.Period may be 0
// after
p := cfg.Period
if p < time.Millisecond { p = time.Millisecond }
t := trigger.AfterProcessingTime().AlignedTo(p, time.Time{})
Defensive patterns

Strategy: validation

Validate before calling

if period < time.Millisecond { return fmt.Errorf("alignment period must be >= 1ms, got %v", period) }

Try / catch

defer func() {
    if r := recover(); r != nil {
        if strings.Contains(fmt.Sprint(r), "alignment period") { return ErrPeriodTooSmall }
        panic(r)
    }
}()

Prevention

When it happens

Trigger: Calling AlignedTo with period < time.Millisecond, e.g. AlignedTo(100 * time.Microsecond, time.Time{}), or a period computed from config as 0.

Common situations: Unset config value for the alignment period (0 duration); microsecond-level alignment copied from another framework; unit mistakes in duration arithmetic.

Understand the failure class

Background: "invalid duration" / "failed to parse duration": why your timeout, interval, or TTL string is rejected and which formats each library accepts — this error's family across 32 libraries.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/64d7f0b95e9ed634. Report an issue: GitHub.