Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions docs/old-reference-guide/modules/events/pages/infrastructure.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,45 @@ public class AxonConfig {
--
====

[[embedded-event-store-spring-boot-configuration]]
==== Spring Boot configuration

When using the Spring Boot auto-configuration, the `EmbeddedEventStore` can be tuned through `application.properties` without requiring a custom bean definition.

[source,properties]
----
# Maximum number of events held in the shared tail cache.
# Only relevant when optimize-event-consumption is true.
# Note: this is a count-based limit, not a byte-based one. Large events can still cause
# significant memory pressure at the default value. Reduce this if your events are large.
axon.eventstore.cached-events=10000

# Whether to share a single tail cache between all tailing TrackingEventProcessors.
# When enabled, processors at the head of the stream share a cache, which reduces
# database load. When disabled, each processor reads from the store independently.
# Disable if you experience memory pressure during replay or migration.
axon.eventstore.optimize-event-consumption=true

# How long the producer thread waits before polling for new events from the backing store
# when no local commit has woken it up. Mainly affects cross-node event latency.
axon.eventstore.fetch-delay=1000ms

# How often the store checks whether any tailing consumers have fallen behind the shared
# cache and need to be switched to a dedicated database connection.
axon.eventstore.cleanup-delay=10000ms
----

[NOTE]
.Memory and the event cache
====
The `cached-events` limit is a *count*, not a byte budget.
If your application processes large events (for example, during a bulk migration), the cache can consume significantly more heap than expected.
For instance, 10,000 events of 100 KB each would require ~1 GB of heap just for the cache.
Consider reducing `cached-events` or disabling `optimize-event-consumption` entirely in environments where event size is variable or replay-heavy workloads are expected.

The JVM system property `optimize-event-consumption` (e.g., `-Doptimize-event-consumption=false`) continues to work as before and takes precedence over this property.
====

=== Event store utilities

Axon provides a number of Event Storage Engines that may be useful in certain circumstances.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ public static class Builder extends AbstractEventStore.Builder {
private ThreadFactory threadFactory = new AxonThreadFactory(THREAD_GROUP);
private boolean optimizeEventConsumption = fetchEventConsumptionSystemPropertyOrDefault();

// Default to optimize event consumption of no property has been set
// Default to optimize event consumption if no property has been set
private static boolean fetchEventConsumptionSystemPropertyOrDefault() {
String optimizeEventConsumptionSystemProperty = System.getProperty(
OPTIMIZE_EVENT_CONSUMPTION_SYSTEM_PROPERTY);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ public static class Builder extends AbstractEventStore.Builder {
private ThreadFactory threadFactory = new AxonThreadFactory(THREAD_GROUP);
private boolean optimizeEventConsumption = fetchEventConsumptionSystemPropertyOrDefault();

// Default to optimize event consumption of no property has been set
// Default to optimize event consumption if no property has been set
private static boolean fetchEventConsumptionSystemPropertyOrDefault() {
String optimizeEventConsumptionSystemProperty = System.getProperty(
OPTIMIZE_EVENT_CONSUMPTION_SYSTEM_PROPERTY);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright (c) 2010-2026. Axon Framework
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.axonframework.springboot;

import org.springframework.boot.context.properties.ConfigurationProperties;

import java.time.Duration;

/**
* Properties describing the settings for the default {@link org.axonframework.eventsourcing.eventstore.EmbeddedEventStore}.
*
* @author Allard Buijze
* @since 4.13.1
*/
@ConfigurationProperties("axon.eventstore")
public class EmbeddedEventStoreProperties {

/**
* Whether event consumption should be optimized between Event Streams. When enabled, distinct Event Consumers will
* read events from the same shared stream as soon as they reach the head of the stream, reducing the number of
* database queries at the cost of additional memory use and background thread activity.
* <p>
* Enabled by default. Can be disabled if the application experiences memory pressure during replay or migration,
* such as when a new TrackingEventProcessor starts replaying a large number of events.
* <p>
* Note: this setting can also be configured with the JVM system property {@code optimize-event-consumption}.
*/
private boolean optimizeEventConsumption = true;

/**
* The maximum number of events in the cache shared between the streams of tracking event processors. Only
* relevant when {@code optimizeEventConsumption} is {@code true}. Defaults to {@code 10000}.
*/
private int cachedEvents = 10000;

/**
* The delay between attempts to fetch new events from the backing storage engine. Defaults to
* {@code 1000ms}.
*/
private Duration fetchDelay = Duration.ofMillis(1000);

/**
* The delay between cache cleanup attempts. Defaults to {@code 10000ms}.
*/
private Duration cleanupDelay = Duration.ofMillis(10000);

/**
* Returns whether event consumption optimization is enabled.
*
* @return {@code true} if optimization is enabled, {@code false} otherwise
*/
public boolean isOptimizeEventConsumption() {
return optimizeEventConsumption;
}

/**
* Sets whether event consumption should be optimized between Event Streams.
*
* @param optimizeEventConsumption {@code true} to enable optimization, {@code false} to disable it
*/
public void setOptimizeEventConsumption(boolean optimizeEventConsumption) {
this.optimizeEventConsumption = optimizeEventConsumption;
}

/**
* Returns the maximum number of events in the shared cache.
*
* @return the maximum number of cached events
*/
public int getCachedEvents() {
return cachedEvents;
}

/**
* Sets the maximum number of events in the shared cache.
*
* @param cachedEvents the maximum number of cached events
*/
public void setCachedEvents(int cachedEvents) {
this.cachedEvents = cachedEvents;
}

/**
* Returns the delay between fetch attempts.
*
* @return the fetch delay as a {@link Duration}
*/
public Duration getFetchDelay() {
return fetchDelay;
}

/**
* Sets the delay between fetch attempts.
*
* @param fetchDelay the fetch delay as a {@link Duration}
*/
public void setFetchDelay(Duration fetchDelay) {
this.fetchDelay = fetchDelay;
}

/**
* Returns the delay between cache cleanup attempts.
*
* @return the cleanup delay as a {@link Duration}
*/
public Duration getCleanupDelay() {
return cleanupDelay;
}

/**
* Sets the delay between cache cleanup attempts.
*
* @param cleanupDelay the cleanup delay as a {@link Duration}
*/
public void setCleanupDelay(Duration cleanupDelay) {
this.cleanupDelay = cleanupDelay;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
import org.axonframework.serialization.xml.XStreamSerializer;
import org.axonframework.spring.eventsourcing.SpringAggregateSnapshotter;
import org.axonframework.springboot.DistributedCommandBusProperties;
import org.axonframework.springboot.EmbeddedEventStoreProperties;
import org.axonframework.springboot.EventProcessorProperties;
import org.axonframework.springboot.SerializerProperties;
import org.axonframework.springboot.TagsConfigurationProperties;
Expand Down Expand Up @@ -109,24 +110,28 @@
@AutoConfiguration
@AutoConfigureAfter(EventProcessingAutoConfiguration.class)
@EnableConfigurationProperties(value = {
EmbeddedEventStoreProperties.class,
EventProcessorProperties.class,
DistributedCommandBusProperties.class,
SerializerProperties.class,
TagsConfigurationProperties.class
})
public class AxonAutoConfiguration implements BeanClassLoaderAware {

private final EmbeddedEventStoreProperties embeddedEventStoreProperties;
private final EventProcessorProperties eventProcessorProperties;
private final SerializerProperties serializerProperties;
private final TagsConfigurationProperties tagsConfigurationProperties;
private final ApplicationContext applicationContext;

private ClassLoader beanClassLoader;

public AxonAutoConfiguration(EventProcessorProperties eventProcessorProperties,
public AxonAutoConfiguration(EmbeddedEventStoreProperties embeddedEventStoreProperties,
EventProcessorProperties eventProcessorProperties,
SerializerProperties serializerProperties,
TagsConfigurationProperties tagsConfigurationProperties,
ApplicationContext applicationContext) {
this.embeddedEventStoreProperties = embeddedEventStoreProperties;
this.eventProcessorProperties = eventProcessorProperties;
this.serializerProperties = serializerProperties;
this.tagsConfigurationProperties = tagsConfigurationProperties;
Expand Down Expand Up @@ -291,6 +296,10 @@ public EmbeddedEventStore eventStore(EventStorageEngine storageEngine, Configura
.storageEngine(storageEngine)
.messageMonitor(configuration.messageMonitor(EventStore.class, "eventStore"))
.spanFactory(configuration.getComponent(EventBusSpanFactory.class))
.optimizeEventConsumption(embeddedEventStoreProperties.isOptimizeEventConsumption())
.cachedEvents(embeddedEventStoreProperties.getCachedEvents())
.fetchDelay(embeddedEventStoreProperties.getFetchDelay().toMillis())
.cleanupDelay(embeddedEventStoreProperties.getCleanupDelay().toMillis())
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,33 @@ void ambiguousBeanDependenciesThrowException() {
});
}

@Test
void embeddedEventStoreDefaultsToOptimizeEventConsumptionEnabled() throws Exception {
new ApplicationContextRunner()
.withUserConfiguration(MinimalContext.class)
.withPropertyValues("axon.axonserver.enabled=false")
.run(context -> {
EmbeddedEventStore eventStore = context.getBean(EmbeddedEventStore.class);
java.lang.reflect.Field field = EmbeddedEventStore.class.getDeclaredField("optimizeEventConsumption");
field.setAccessible(true);
assertTrue((boolean) field.get(eventStore));
});
}

@Test
void embeddedEventStoreOptimizeEventConsumptionCanBeDisabledViaProperties() throws Exception {
new ApplicationContextRunner()
.withUserConfiguration(MinimalContext.class)
.withPropertyValues("axon.axonserver.enabled=false",
"axon.eventstore.optimize-event-consumption=false")
.run(context -> {
EmbeddedEventStore eventStore = context.getBean(EmbeddedEventStore.class);
java.lang.reflect.Field field = EmbeddedEventStore.class.getDeclaredField("optimizeEventConsumption");
field.setAccessible(true);
assertFalse((boolean) field.get(eventStore));
});
}

@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
@Configuration
Expand Down Expand Up @@ -318,6 +345,17 @@ public static class SomeOtherComponent {
}
}

@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
@Configuration
public static class MinimalContext {

@Bean
public EventStorageEngine storageEngine() {
return new InMemoryEventStorageEngine();
}
}

public static class CustomResource {

}
Expand Down
Loading