Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
78daa80
perf(cache): wire Redis metrics, fix REST GET cache path, cache ReadB…
harshach Apr 17, 2026
056902a
perf(cache): cross-instance cache invalidation via Redis pub/sub
harshach Apr 17, 2026
1a16a8f
perf(cache): single-flight stampede protection on bundle cache
harshach Apr 17, 2026
55220e6
perf(cache): rewrite warmup with bulk SQL + pipelined Redis writes
harshach Apr 17, 2026
fa64ba9
perf(cache): cache certification + container refs, 0 DB queries per w…
harshach Apr 17, 2026
9bfddb7
perf(cache): fix write-through shape + tighten invalidation on updates
harshach Apr 17, 2026
4bad8a9
test(cache): add Redis testcontainer support + mysql-elasticsearch-re…
harshach Apr 17, 2026
1c385f9
perf(cache): invalidate stale cache entries on rename cascade and dir…
harshach Apr 17, 2026
db226ef
perf(cache): invalidate cache entries on batched CSV import updates
harshach Apr 17, 2026
a322960
perf(cache): lowercase user FQN in name-based cache loader
harshach Apr 17, 2026
c3442f1
test(it): raise PrometheusResourceIT timeouts for loaded CI runs
harshach Apr 17, 2026
55d9a11
test(it): raise TagResourceIT search-index timeout to 90s
harshach Apr 17, 2026
33e28ef
fix(search): default entityStatus to Unprocessed in search index doc
harshach Apr 17, 2026
afbe591
test(it): retry BaseEntityIT testBulkFluentAPI verification under load
harshach Apr 17, 2026
92c2c9b
test(it): raise TestCaseResourceIT awaitility timeouts to 90s
harshach Apr 17, 2026
ff40a5f
test(it): raise BaseEntityIT checkCreatedEntity search-index timeout …
harshach Apr 17, 2026
f5786e7
test(it): extend testBulkFluentAPI retry window to 60s
harshach Apr 17, 2026
a745f14
perf(testCase): retry bulk logical-suite insert on MySQL deadlock
harshach Apr 17, 2026
898f290
test(it): raise TestCaseResourceIT awaitility timeouts to 180s
harshach Apr 17, 2026
e0336e7
Merge remote-tracking branch 'origin/main' into perf/redis-cache-metr…
harshach Apr 17, 2026
1bf27e0
perf(cache): address PR review — Postgres portability, single-flight,…
harshach Apr 18, 2026
eaedf39
perf(cache): address PR review — rediss:// SSL, pipeline error handli…
harshach Apr 18, 2026
b32a5d8
refactor(jdbi): extract DeadlockRetry utility with resilience4j backoff
harshach Apr 18, 2026
d626a4d
fix(cache): log root cause of first Redis pipeline failure
harshach Apr 21, 2026
061534d
fix: address Copilot review — counters, lock leak, txn retry, gating
harshach Apr 21, 2026
da4eb03
Merge branch 'main' into perf/redis-cache-metrics-and-indexes
harshach Apr 21, 2026
95a913d
fix(cache): address review — health-check, pipeline failure accountin…
mohityadav766 Apr 23, 2026
1aaf82f
fix(cache): abort warmup when provider flips to unavailable mid-run
mohityadav766 Apr 23, 2026
d62795c
fix(cache): address two new Copilot findings — PubSub leak + deadlock…
mohityadav766 Apr 23, 2026
2215a2a
Merge branch 'main' into perf/redis-cache-metrics-and-indexes
ShaileshParmar11 Apr 23, 2026
d3d3b0a
Merge branch 'main' into perf/redis-cache-metrics-and-indexes
mohityadav766 Apr 23, 2026
2b73ddf
fix(cache): two more Copilot findings — user FQN case-fold + awaitAll…
mohityadav766 Apr 23, 2026
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
2 changes: 2 additions & 0 deletions conf/openmetadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,8 @@ cache:
# Connection pool settings
poolSize: ${CACHE_REDIS_POOL_SIZE:-64}
connectTimeoutMs: ${CACHE_REDIS_CONNECT_TIMEOUT:-2000}
# Per-command timeout. Bounds request-thread blocking when Redis is slow.
commandTimeoutMs: ${CACHE_REDIS_COMMAND_TIMEOUT:-300}

# AWS ElastiCache IAM Authentication (only if using ElastiCache)
aws:
Expand Down
86 changes: 86 additions & 0 deletions docker/development/docker-compose.multiserver.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Copyright 2021 Collate
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

# Adds a second OM instance that shares MySQL/Elasticsearch/Redis with the
# primary one in docker-compose.yml. Used to validate that pub/sub
# invalidation keeps per-instance Guava caches coherent.
#
# Usage:
# docker compose -f docker-compose.yml -f docker-compose.redis.yml \
# -f docker-compose.multiserver.yml up -d
services:
openmetadata-server-2:
image: development-openmetadata-server
build:
context: ../../.
dockerfile: docker/development/Dockerfile
container_name: openmetadata_server_2
restart: always
networks:
- local_app_net
depends_on:
mysql:
condition: service_healthy
elasticsearch:
condition: service_healthy
redis:
condition: service_healthy
ports:
- "8587:8585"
- "8588:8586"
environment:
OPENMETADATA_CLUSTER_NAME: openmetadata
SERVER_PORT: 8585
SERVER_ADMIN_PORT: 8586
LOG_LEVEL: INFO
FERNET_KEY: jJ/9sz0g0OHxsfxOoSfdFdmk3ysNmPRnH3TUAbz3IHA=
DB_DRIVER_CLASS: com.mysql.cj.jdbc.Driver
DB_SCHEME: mysql
DB_USE_SSL: "false"
DB_USER: openmetadata_user
DB_USER_PASSWORD: openmetadata_password
DB_HOST: mysql
DB_PORT: 3306
DB_PARAMS: allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC
OM_DATABASE: openmetadata_db
ELASTICSEARCH_HOST: elasticsearch
ELASTICSEARCH_PORT: 9200
ELASTICSEARCH_SCHEME: http
SEARCH_TYPE: elasticsearch
ELASTICSEARCH_CLUSTER_ALIAS: openmetadata
AUTHENTICATION_PROVIDER: basic
AUTHENTICATION_ENABLE_SELF_SIGNUP: "true"
AUTHORIZER_CLASS_NAME: org.openmetadata.service.security.DefaultAuthorizer
AUTHORIZER_REQUEST_FILTER: org.openmetadata.service.security.JwtFilter
AUTHORIZER_ADMIN_PRINCIPALS: "[admin]"
AUTHORIZER_PRINCIPAL_DOMAIN: open-metadata.org
AUTHORIZER_ALLOWED_DOMAINS: "[]"
AUTHORIZER_ALLOWED_REGISTRATION_DOMAIN: '["all"]'
AUTHORIZER_INGESTION_PRINCIPALS: "[ingestion-bot]"
AUTHENTICATION_RESPONSE_TYPE: id_token
AUTHENTICATION_CLIENT_TYPE: public
AUTHENTICATION_PUBLIC_KEYS: "[http://openmetadata-server-2:8585/api/v1/system/config/jwks]"
AUTHENTICATION_AUTHORITY: https://accounts.google.com
AUTHENTICATION_JWT_PRINCIPAL_CLAIMS: "[email,preferred_username,sub]"
RSA_PUBLIC_KEY_FILE_PATH: ./conf/public_key.der
RSA_PRIVATE_KEY_FILE_PATH: ./conf/private_key.der
JWT_ISSUER: open-metadata.org
JWT_KEY_ID: Gb389a-9f76-gdjs-a92j-0242bk94356
PIPELINE_SERVICE_CLIENT_ENDPOINT: http://ingestion:8080
PIPELINE_SERVICE_CLIENT_CLASS_NAME: org.openmetadata.service.clients.pipeline.airflow.AirflowRESTClient
AIRFLOW_USERNAME: admin
AIRFLOW_PASSWORD: admin
AIRFLOW_TIMEOUT: 10
SECRET_MANAGER: db
SERVER_HOST_API_URL: http://openmetadata-server-2:8585/api
EVENT_MONITOR: prometheus
OPENMETADATA_HEAP_OPTS: "-Xmx1G -Xms1G"
CACHE_PROVIDER: redis
CACHE_REDIS_URL: redis://redis:6379
CACHE_REDIS_AUTH_TYPE: NONE
CACHE_REDIS_KEYSPACE: om:dev
CACHE_ENTITY_TTL: 3600
CACHE_RELATIONSHIP_TTL: 3600
CACHE_TAG_TTL: 3600
CACHE_REDIS_COMMAND_TIMEOUT: 300
36 changes: 36 additions & 0 deletions docker/development/docker-compose.redis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright 2021 Collate
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

# Override that adds a Redis cache to the development stack.
# Usage:
# docker compose -f docker-compose.yml -f docker-compose.redis.yml up -d
services:
redis:
image: redis:7-alpine
container_name: openmetadata_redis
restart: always
command: ["redis-server", "--appendonly", "no", "--save", "", "--maxmemory", "512mb", "--maxmemory-policy", "allkeys-lru"]
networks:
- local_app_net
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5

openmetadata-server:
depends_on:
redis:
condition: service_healthy
environment:
CACHE_PROVIDER: redis
CACHE_REDIS_URL: redis://redis:6379
CACHE_REDIS_AUTH_TYPE: NONE
CACHE_REDIS_KEYSPACE: om:dev
CACHE_ENTITY_TTL: 3600
CACHE_RELATIONSHIP_TTL: 3600
CACHE_TAG_TTL: 3600
CACHE_REDIS_COMMAND_TIMEOUT: 300
89 changes: 89 additions & 0 deletions openmetadata-integration-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,95 @@
</plugins>
</build>
</profile>
<!-- MySQL + Elasticsearch + Redis (Redis cache enabled) -->
<profile>
<id>mysql-elasticsearch-redis</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${maven.failsafe.version}</version>
<executions>
<execution>
<id>sequential-tests</id>
<goals>
<goal>integration-test</goal>
</goals>
<configuration>
<forkCount>1</forkCount>
<reuseForks>true</reuseForks>
<argLine>-Xmx4096m -XX:+UseG1GC</argLine>
<includes>
<include>**/TagRecognizerFeedbackIT.java</include>
<include>**/WorkflowDefinitionResourceIT.java</include>
<include>**/AppsResourceIT.java</include>
<include>**/SystemResourceIT.java</include>
<include>**/VectorEmbeddingIntegrationIT.java</include>
</includes>
<systemPropertyVariables>
<databaseType>mysql</databaseType>
<databaseImage>mysql:8.3.0</databaseImage>
<searchType>elasticsearch</searchType>
<searchImage>docker.elastic.co/elasticsearch/elasticsearch:9.3.0</searchImage>
<cacheProvider>redis</cacheProvider>
<redisImage>redis:7-alpine</redisImage>

<junit.jupiter.extensions.autodetection.enabled>true</junit.jupiter.extensions.autodetection.enabled>
<junit.jupiter.execution.parallel.enabled>false</junit.jupiter.execution.parallel.enabled>
</systemPropertyVariables>
<trimStackTrace>false</trimStackTrace>
<reportFormat>plain</reportFormat>
<useFile>true</useFile>
</configuration>
</execution>
<execution>
<id>parallel-tests</id>
<goals>
<goal>integration-test</goal>
</goals>
<configuration>
<forkCount>1</forkCount>
<reuseForks>true</reuseForks>
<argLine>-Xmx4096m -XX:+UseG1GC</argLine>
<includes>
<include>**/*IT.java</include>
<include>**/*Test.java</include>
</includes>
<excludes>
<exclude>**/TagRecognizerFeedbackIT.java</exclude>
<exclude>**/WorkflowDefinitionResourceIT.java</exclude>
<exclude>**/AppsResourceIT.java</exclude>
<exclude>**/SystemResourceIT.java</exclude>
<exclude>**/VectorEmbeddingIntegrationIT.java</exclude>
</excludes>
<systemPropertyVariables>
<databaseType>mysql</databaseType>
<databaseImage>mysql:8.3.0</databaseImage>
<searchType>elasticsearch</searchType>
<searchImage>docker.elastic.co/elasticsearch/elasticsearch:9.3.0</searchImage>
<cacheProvider>redis</cacheProvider>
<redisImage>redis:7-alpine</redisImage>

<junit.jupiter.extensions.autodetection.enabled>true</junit.jupiter.extensions.autodetection.enabled>
<junit.jupiter.execution.parallel.enabled>true</junit.jupiter.execution.parallel.enabled>
</systemPropertyVariables>
<trimStackTrace>false</trimStackTrace>
<reportFormat>plain</reportFormat>
<useFile>true</useFile>
</configuration>
</execution>
<execution>
<id>verify</id>
<goals>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<!-- RDF integration tests profile -->
<profile>
<id>postgres-rdf-tests</id>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,12 @@ public class TestSuiteBootstrap implements LauncherSessionListener {
private static String databaseType;
private static String searchType;
private static boolean rdfEnabled;
private static String cacheProvider;

private static JdbcDatabaseContainer<?> DATABASE_CONTAINER;
private static GenericContainer<?> SEARCH_CONTAINER;
private static GenericContainer<?> FUSEKI_CONTAINER;
private static GenericContainer<?> REDIS_CONTAINER;
private static K3sContainer K3S_CONTAINER;
private static DropwizardAppExtension<OpenMetadataApplicationConfig> APP;
private static Jdbi jdbi;
Expand All @@ -141,6 +143,10 @@ public class TestSuiteBootstrap implements LauncherSessionListener {
private static int searchPort;
private static String fusekiEndpoint;
private static String kubeConfigYaml;
private static String redisUrl;

private static final String DEFAULT_REDIS_IMAGE = "redis:7-alpine";
private static final int REDIS_PORT = 6379;

@Override
public void launcherSessionOpened(LauncherSession session) {
Expand All @@ -153,11 +159,13 @@ public void launcherSessionOpened(LauncherSession session) {
databaseType = System.getProperty("databaseType", "postgres");
searchType = System.getProperty("searchType", "elasticsearch");
rdfEnabled = Boolean.parseBoolean(System.getProperty("enableRdf", "false"));
cacheProvider = System.getProperty("cacheProvider", "none");

LOG.info("=== TestSuiteBootstrap: Starting test infrastructure ===");
LOG.info("Database type: {}", databaseType);
LOG.info("Search type: {}", searchType);
LOG.info("RDF enabled: {}", rdfEnabled);
LOG.info("Cache provider: {}", cacheProvider);
boolean k8sEnabled = isK8sTestsRequested();
LOG.info("K8s tests enabled: {}", k8sEnabled);
long startTime = System.currentTimeMillis();
Expand All @@ -168,6 +176,9 @@ public void launcherSessionOpened(LauncherSession session) {
if (rdfEnabled) {
startFuseki();
}
if (isRedisEnabled()) {
startRedis();
}
if (k8sEnabled) {
startK3s();
}
Expand All @@ -180,6 +191,9 @@ public void launcherSessionOpened(LauncherSession session) {
if (rdfEnabled) {
LOG.info("Fuseki SPARQL: {}", fusekiEndpoint);
}
if (isRedisEnabled()) {
LOG.info("Redis: {}", redisUrl);
}
if (k8sEnabled) {
LOG.info("K3s Kubernetes: enabled");
}
Expand Down Expand Up @@ -340,6 +354,58 @@ private void startSearch() {
}
}

private void startRedis() {
String image = System.getProperty("redisImage", DEFAULT_REDIS_IMAGE);
LOG.info("Starting Redis container with image: {}", image);
REDIS_CONTAINER =
new GenericContainer<>(DockerImageName.parse(image))
.withExposedPorts(REDIS_PORT)
.withCommand(
"redis-server",
"--appendonly",
"no",
"--save",
"",
"--maxmemory",
"512mb",
"--maxmemory-policy",
"allkeys-lru")
.waitingFor(Wait.forListeningPort().withStartupTimeout(Duration.ofMinutes(1)));
REDIS_CONTAINER.start();
redisUrl =
String.format(
"redis://%s:%d", REDIS_CONTAINER.getHost(), REDIS_CONTAINER.getMappedPort(REDIS_PORT));
LOG.info("Redis started: {}", redisUrl);
}

public static boolean isRedisEnabled() {
return "redis".equalsIgnoreCase(cacheProvider);
}

public static String getRedisUrl() {
return redisUrl;
}

private void configureCache(OpenMetadataApplicationConfig config) {
if (!isRedisEnabled()) {
return;
}
org.openmetadata.service.cache.CacheConfig cacheConfig = config.getCacheConfig();
cacheConfig.provider = org.openmetadata.service.cache.CacheConfig.Provider.redis;
cacheConfig.redis.url = redisUrl;
cacheConfig.redis.authType = org.openmetadata.service.cache.CacheConfig.AuthType.NONE;
cacheConfig.redis.keyspace = "om:it:" + System.currentTimeMillis();
cacheConfig.redis.commandTimeoutMs = 1000;
cacheConfig.entityTtlSeconds = 3600;
cacheConfig.relationshipTtlSeconds = 3600;
cacheConfig.tagTtlSeconds = 3600;
config.setCacheConfig(cacheConfig);
LOG.info(
"Configured Redis cache: url={} keyspace={}",
cacheConfig.redis.url,
cacheConfig.redis.keyspace);
}

private void startFuseki() {
String image = System.getProperty("rdfContainerImage", DEFAULT_FUSEKI_IMAGE);
LOG.info("Starting Fuseki SPARQL container...");
Expand Down Expand Up @@ -451,6 +517,7 @@ private void startApplication() throws Exception {

configurePipelineServiceClient(config);
configureRdf(config);
configureCache(config);

IndexMappingLoader.init(getBaseSearchConfig());

Expand Down Expand Up @@ -718,6 +785,14 @@ private void cleanup() {
LOG.warn("Error stopping Fuseki container", e);
}

try {
if (REDIS_CONTAINER != null) {
REDIS_CONTAINER.stop();
}
} catch (Exception e) {
LOG.warn("Error stopping Redis container", e);
}

try {
if (K3S_CONTAINER != null) {
K3S_CONTAINER.stop();
Expand Down
Loading
Loading