diff --git a/.env.docker-compose b/.env.docker-compose index 957eccb959..e3f797d4d3 100644 --- a/.env.docker-compose +++ b/.env.docker-compose @@ -96,6 +96,18 @@ API_DB_POOL_CONNECTION_TIMEOUT_MS=100000 API_DB_KEEP_ALIVE_MS=60000 API_DB_LEAK_CONNECTIONS_WARNING_MS=60000 +## Indexer DB tuning / debugging +INDEXER_DB_SHOW_SQL=false +INDEXER_DB_MONITOR_PERFORMANCE=false + +## Indexer DB pool tuning +INDEXER_DB_POOL_MIN_COUNT=12 +INDEXER_DB_POOL_MAX_COUNT=12 +INDEXER_DB_POOL_MAX_LIFETIME_MS=2000000 +INDEXER_DB_POOL_CONNECTION_TIMEOUT_MS=100000 +INDEXER_DB_KEEP_ALIVE_MS=60000 +INDEXER_DB_LEAK_CONNECTIONS_WARNING_MS=60000 + SYNC_GRACE_SLOTS_COUNT=100 ## Monitoring variables diff --git a/.env.docker-compose-preprod b/.env.docker-compose-preprod index 3e40bbc9d8..063f8f016c 100644 --- a/.env.docker-compose-preprod +++ b/.env.docker-compose-preprod @@ -95,6 +95,18 @@ API_DB_POOL_CONNECTION_TIMEOUT_MS=100000 API_DB_KEEP_ALIVE_MS=60000 API_DB_LEAK_CONNECTIONS_WARNING_MS=60000 +## Indexer DB tuning / debugging +INDEXER_DB_SHOW_SQL=false +INDEXER_DB_MONITOR_PERFORMANCE=false + +## Indexer DB pool tuning +INDEXER_DB_POOL_MIN_COUNT=12 +INDEXER_DB_POOL_MAX_COUNT=12 +INDEXER_DB_POOL_MAX_LIFETIME_MS=2000000 +INDEXER_DB_POOL_CONNECTION_TIMEOUT_MS=100000 +INDEXER_DB_KEEP_ALIVE_MS=60000 +INDEXER_DB_LEAK_CONNECTIONS_WARNING_MS=60000 + SYNC_GRACE_SLOTS_COUNT=100 ## When set to true, the parsing / processing blockchain data even if an error occurs. diff --git a/.env.docker-compose-profile-advanced-level b/.env.docker-compose-profile-advanced-level index c866ff775b..8910a70b79 100644 --- a/.env.docker-compose-profile-advanced-level +++ b/.env.docker-compose-profile-advanced-level @@ -1,6 +1,10 @@ API_DB_POOL_MIN_COUNT=100 API_DB_POOL_MAX_COUNT=550 +## Indexer DB pool performance tuning +INDEXER_DB_POOL_MIN_COUNT=50 +INDEXER_DB_POOL_MAX_COUNT=150 + DB_POSTGRES_MAX_CONNECTIONS=600 DB_POSTGRES_SHARED_BUFFERS=32GB DB_POSTGRES_EFFECTIVE_CACHE_SIZE=32GB diff --git a/.env.docker-compose-profile-entry-level b/.env.docker-compose-profile-entry-level index 710fe1a116..61f07dbb09 100644 --- a/.env.docker-compose-profile-entry-level +++ b/.env.docker-compose-profile-entry-level @@ -2,6 +2,10 @@ API_DB_POOL_MIN_COUNT=12 API_DB_POOL_MAX_COUNT=12 +## Indexer DB pool performance tuning +INDEXER_DB_POOL_MIN_COUNT=12 +INDEXER_DB_POOL_MAX_COUNT=12 + ## PostgreSQL Tuning for Entry-Level Profile DB_POSTGRES_MAX_CONNECTIONS=120 DB_POSTGRES_SHARED_BUFFERS=1GB diff --git a/.env.docker-compose-profile-mid-level b/.env.docker-compose-profile-mid-level index 2f8b919430..556087e06b 100644 --- a/.env.docker-compose-profile-mid-level +++ b/.env.docker-compose-profile-mid-level @@ -1,6 +1,10 @@ API_DB_POOL_MIN_COUNT=150 API_DB_POOL_MAX_COUNT=150 +## Indexer DB pool performance tuning +INDEXER_DB_POOL_MIN_COUNT=50 +INDEXER_DB_POOL_MAX_COUNT=50 + DB_POSTGRES_MAX_CONNECTIONS=300 DB_POSTGRES_SHARED_BUFFERS=4GB DB_POSTGRES_EFFECTIVE_CACHE_SIZE=8GB diff --git a/.gitignore b/.gitignore index b715dd51f7..c023ee6ece 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,8 @@ settings.xml !docker/.env.docker-profile-entry-level !docker/.env.docker-profile-mid-level !.claude/* -.claude/settings.local.json \ No newline at end of file +.claude/settings.local.json + +# Helm build artifacts (generated by helm package — do not commit) +helm/**/charts/*.tgz +helm/**/Chart.lock \ No newline at end of file diff --git a/api/src/main/java/org/cardanofoundation/rosetta/api/call/service/CallService.java b/api/src/main/java/org/cardanofoundation/rosetta/api/call/service/CallService.java index f0e72d8914..6e46558606 100644 --- a/api/src/main/java/org/cardanofoundation/rosetta/api/call/service/CallService.java +++ b/api/src/main/java/org/cardanofoundation/rosetta/api/call/service/CallService.java @@ -5,6 +5,7 @@ import org.openapitools.client.model.CallResponse; import javax.annotation.Nullable; +import java.util.List; import java.util.Map; public interface CallService { @@ -24,4 +25,9 @@ public interface CallService { */ CallResponse markParseErrorBlockChecked(Map params); + /** + * Get a list of supported method names for the /call endpoint + */ + List getSupportedMethods(); + } \ No newline at end of file diff --git a/api/src/main/java/org/cardanofoundation/rosetta/api/call/service/CallServiceImpl.java b/api/src/main/java/org/cardanofoundation/rosetta/api/call/service/CallServiceImpl.java index 0bf41f3193..cee6916d00 100644 --- a/api/src/main/java/org/cardanofoundation/rosetta/api/call/service/CallServiceImpl.java +++ b/api/src/main/java/org/cardanofoundation/rosetta/api/call/service/CallServiceImpl.java @@ -28,6 +28,11 @@ public class CallServiceImpl implements CallService { private final BlockParsingErrorReviewService blockParsingErrorReviewService; + @Override + public List getSupportedMethods() { + return List.of(METHOD_GET_PARSE_ERROR_BLOCKS, METHOD_MARK_PARSE_ERROR_BLOCK_CHECKED); + } + @Override public CallResponse processCallRequest(CallRequest callRequest) { String method = callRequest.getMethod(); diff --git a/api/src/main/java/org/cardanofoundation/rosetta/api/construction/service/ConstructionApiServiceImpl.java b/api/src/main/java/org/cardanofoundation/rosetta/api/construction/service/ConstructionApiServiceImpl.java index 72db48a814..f2b02a9fc7 100644 --- a/api/src/main/java/org/cardanofoundation/rosetta/api/construction/service/ConstructionApiServiceImpl.java +++ b/api/src/main/java/org/cardanofoundation/rosetta/api/construction/service/ConstructionApiServiceImpl.java @@ -63,9 +63,13 @@ public ConstructionDeriveResponse constructionDeriveService( } // Default address type is enterprise - AddressType addressType = - metadata.getAddressType() != null ? AddressType.findByValue(metadata.getAddressType()) - : null; + AddressType addressType = null; + if (metadata.getAddressType() != null && !metadata.getAddressType().trim().isEmpty()) { + addressType = AddressType.findByValue(metadata.getAddressType()); + if (addressType == null) { + throw ExceptionFactory.invalidAddressTypeError(); + } + } addressType = addressType != null ? addressType : AddressType.ENTERPRISE; PublicKey stakingCredential = null; diff --git a/api/src/main/java/org/cardanofoundation/rosetta/api/network/service/NetworkServiceImpl.java b/api/src/main/java/org/cardanofoundation/rosetta/api/network/service/NetworkServiceImpl.java index bf69dfbb05..a02fe0c4a0 100644 --- a/api/src/main/java/org/cardanofoundation/rosetta/api/network/service/NetworkServiceImpl.java +++ b/api/src/main/java/org/cardanofoundation/rosetta/api/network/service/NetworkServiceImpl.java @@ -8,6 +8,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.cardanofoundation.rosetta.api.call.service.CallService; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Service; @@ -42,6 +43,7 @@ public class NetworkServiceImpl implements NetworkService { private final ResourceLoader resourceLoader; private final GenesisDataProvider genesisDataProvider; private final SyncStatusService syncStatusService; + private final CallService callService; @Value("${cardano.rosetta.GENESIS_SHELLEY_PATH}") private String genesisShelleyPath; @@ -100,7 +102,7 @@ public NetworkOptionsResponse getNetworkOptions(NetworkRequest networkRequest) { .sorted(Comparator.comparingInt(Error::getCode)) .toList()) .historicalBalanceLookup(true) - .callMethods(new ArrayList<>()) + .callMethods(callService.getSupportedMethods()) .mempoolCoins(false)) .build(); } diff --git a/api/src/test/java/org/cardanofoundation/rosetta/api/construction/service/DeriveApiTest.java b/api/src/test/java/org/cardanofoundation/rosetta/api/construction/service/DeriveApiTest.java index 676db1cb5b..8dc4ef2ac6 100644 --- a/api/src/test/java/org/cardanofoundation/rosetta/api/construction/service/DeriveApiTest.java +++ b/api/src/test/java/org/cardanofoundation/rosetta/api/construction/service/DeriveApiTest.java @@ -9,6 +9,7 @@ import org.openapitools.client.model.ConstructionDeriveResponse; import org.openapitools.client.model.NetworkIdentifier; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.cardanofoundation.rosetta.api.IntegrationTest; @@ -29,28 +30,44 @@ private ConstructionDeriveRequest getDeriveRequest(String fileName) throws IOExc return request; } - @Test - void deriveAddressTest() throws IOException { - ConstructionDeriveRequest deriveRequest = getDeriveRequest( - "testdata/construction/derive/derive_request.json"); - ConstructionDeriveResponse constructionDeriveResponse = constructionApiService.constructionDeriveService( - deriveRequest); + @Nested + class DeriveAddressTest { + @Test + void shouldReturnCorrectAddress() throws IOException { + ConstructionDeriveRequest deriveRequest = getDeriveRequest( + "testdata/construction/derive/derive_request.json"); + ConstructionDeriveResponse constructionDeriveResponse = constructionApiService.constructionDeriveService( + deriveRequest); - String address = "addr_test1vza5pudxg77g3sdaddecmw8tvc6hmynywn49lltt4fmvn7c6mzywr"; - assertEquals(address, constructionDeriveResponse.getAccountIdentifier().getAddress()); + String address = "addr_test1vza5pudxg77g3sdaddecmw8tvc6hmynywn49lltt4fmvn7c6mzywr"; + assertEquals(address, constructionDeriveResponse.getAccountIdentifier().getAddress()); + } } - @Test - void deriveAddressTestWithInvalidNetworkConfigurationTest() throws IOException { - ConstructionDeriveRequest deriveRequest = getDeriveRequest( - "testdata/construction/derive/derive_request.json"); - deriveRequest.setNetworkIdentifier(new NetworkIdentifier()); - ApiException exception = assertThrows(ApiException. class, - () -> constructionApiService.constructionDeriveService( - deriveRequest)); - assertEquals(4000, exception.getError().getCode()); - assertEquals("Invalid Network configuration", exception.getError().getMessage()); + @Nested + class InvalidNetworkConfigurationTest { - } + @Test + void shouldThrowException() throws IOException { + ConstructionDeriveRequest deriveRequest = getDeriveRequest( + "testdata/construction/derive/derive_request.json"); + deriveRequest.setNetworkIdentifier(new NetworkIdentifier()); + + assertThrows(ApiException.class, + () -> constructionApiService.constructionDeriveService(deriveRequest)); + } + + @Test + void shouldHaveCorrectErrorCodeAndMessage() throws IOException { + ConstructionDeriveRequest deriveRequest = getDeriveRequest( + "testdata/construction/derive/derive_request.json"); + deriveRequest.setNetworkIdentifier(new NetworkIdentifier()); + ApiException exception = assertThrows(ApiException.class, + () -> constructionApiService.constructionDeriveService(deriveRequest)); + + assertEquals(4000, exception.getError().getCode()); + assertEquals("Invalid Network configuration", exception.getError().getMessage()); + } + } } diff --git a/api/src/test/java/org/cardanofoundation/rosetta/api/network/service/NetworkServiceImplTest.java b/api/src/test/java/org/cardanofoundation/rosetta/api/network/service/NetworkServiceImplTest.java index 98c3905959..80eb21eee9 100644 --- a/api/src/test/java/org/cardanofoundation/rosetta/api/network/service/NetworkServiceImplTest.java +++ b/api/src/test/java/org/cardanofoundation/rosetta/api/network/service/NetworkServiceImplTest.java @@ -15,6 +15,7 @@ import org.openapitools.client.model.NetworkRequest; import org.openapitools.client.model.NetworkStatusResponse; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.cardanofoundation.rosetta.EntityGenerator; @@ -32,74 +33,9 @@ class NetworkServiceImplTest extends IntegrationTest { @Autowired private NetworkService networkService; - @Test - void verifyCorrectNetworkTest() { - // void function, no exception expected - networkService.verifyNetworkRequest( - createNetworkIdentifier(Constants.CARDANO_BLOCKCHAIN, Constants.DEVKIT)); - } - - @Test - void verifyWrongNetworkTest() { - NetworkIdentifier wrongNetworkIdentifier = createNetworkIdentifier(Constants.CARDANO_BLOCKCHAIN, - Constants.MAINNET); - ApiException apiException = assertThrows(ApiException.class, - () -> networkService.verifyNetworkRequest(wrongNetworkIdentifier)); - assertEquals(RosettaErrorType.NETWORK_NOT_FOUND.getMessage(), - apiException.getError().getMessage()); - assertEquals(RosettaErrorType.NETWORK_NOT_FOUND.getCode(), apiException.getError().getCode()); - } - - @Test - void verifyWrongBlockchainTest() { - NetworkIdentifier wrongBlockchain = createNetworkIdentifier("Wrong Blockchain", - Constants.DEVKIT); - ApiException apiException = assertThrows(ApiException.class, - () -> networkService.verifyNetworkRequest(wrongBlockchain)); - assertEquals(RosettaErrorType.INVALID_BLOCKCHAIN.getMessage(), - apiException.getError().getMessage()); - assertEquals(RosettaErrorType.INVALID_BLOCKCHAIN.getCode(), apiException.getError().getCode()); - } - - @Test - void networkOptionTest() throws IOException { - //given - NetworkRequest networkRequest = getNetworkRequest(); - //when - NetworkOptionsResponse networkOptions = networkService.getNetworkOptions(networkRequest); - - //then - assertNotNull(networkOptions); - assertEquals(getErrors(), networkOptions.getAllow().getErrors()); - } - - @Test - void networkStatusTest() throws IOException { - //given - NetworkRequest networkRequest = getNetworkRequest(); - //when - NetworkStatusResponse networkStatus = networkService.getNetworkStatus(networkRequest); - //then - assertNotNull(networkStatus); - assertEquals(0, networkStatus.getGenesisBlockIdentifier().getIndex()); - assertEquals("Genesis", - networkStatus.getGenesisBlockIdentifier().getHash()); - - } - - @Test - void networkListTest() { - //given - MetadataRequest metadataRequest = EntityGenerator.givenMetadataRequest(); - //when - NetworkListResponse networkList = networkService.getNetworkList(metadataRequest); - //then - assertNotNull(networkList); - } - private NetworkRequest getNetworkRequest() throws IOException { File file = new File(this.getClass().getClassLoader() - .getResource("testdata/networkIdentifier.json").getFile()); + .getResource("testdata/networkIdentifier.json").getFile()); ObjectMapper mapper = new ObjectMapper(); return mapper.readValue(file, NetworkRequest.class); @@ -107,10 +43,11 @@ private NetworkRequest getNetworkRequest() throws IOException { private List getErrors() throws IOException { File file = new File(this.getClass().getClassLoader() - .getResource("testdata/errors.json").getFile()); + .getResource("testdata/errors.json").getFile()); ObjectMapper mapper = new ObjectMapper(); - return mapper.readValue(file, new TypeReference<>() {}); + return mapper.readValue(file, new TypeReference<>() { + }); } private NetworkIdentifier createNetworkIdentifier(String blockchain, String network) { @@ -120,4 +57,135 @@ private NetworkIdentifier createNetworkIdentifier(String blockchain, String netw .build(); } + @Nested + class VerifyNetworkRequestTest { + + @Test + void shouldNotThrowExceptionForCorrectNetwork() { + // void function, no exception expected + networkService.verifyNetworkRequest( + createNetworkIdentifier(Constants.CARDANO_BLOCKCHAIN, Constants.DEVKIT)); + } + + @Nested + class WrongNetworkTest { + @Test + void shouldThrowApiException() { + NetworkIdentifier wrongNetworkIdentifier = createNetworkIdentifier(Constants.CARDANO_BLOCKCHAIN, + Constants.MAINNET); + assertThrows(ApiException.class, + () -> networkService.verifyNetworkRequest(wrongNetworkIdentifier)); + } + + @Test + void shouldHaveCorrectErrorCodeAndMessage() { + NetworkIdentifier wrongNetworkIdentifier = createNetworkIdentifier(Constants.CARDANO_BLOCKCHAIN, + Constants.MAINNET); + ApiException apiException = assertThrows(ApiException.class, + () -> networkService.verifyNetworkRequest(wrongNetworkIdentifier)); + + assertEquals(RosettaErrorType.NETWORK_NOT_FOUND.getMessage(), + apiException.getError().getMessage()); + assertEquals(RosettaErrorType.NETWORK_NOT_FOUND.getCode(), apiException.getError().getCode()); + } + } + + @Nested + class WrongBlockchainTest { + @Test + void shouldThrowApiException() { + NetworkIdentifier wrongBlockchain = createNetworkIdentifier("Wrong Blockchain", + Constants.DEVKIT); + assertThrows(ApiException.class, + () -> networkService.verifyNetworkRequest(wrongBlockchain)); + } + + @Test + void shouldHaveCorrectErrorCodeAndMessage() { + NetworkIdentifier wrongBlockchain = createNetworkIdentifier("Wrong Blockchain", + Constants.DEVKIT); + ApiException apiException = assertThrows(ApiException.class, + () -> networkService.verifyNetworkRequest(wrongBlockchain)); + + assertEquals(RosettaErrorType.INVALID_BLOCKCHAIN.getMessage(), + apiException.getError().getMessage()); + assertEquals(RosettaErrorType.INVALID_BLOCKCHAIN.getCode(), apiException.getError().getCode()); + } + } + } + + @Nested + class NetworkOptionsTest { + + @Test + void shouldReturnNetworkOptions() throws IOException { + // given + NetworkRequest networkRequest = getNetworkRequest(); + // when + NetworkOptionsResponse networkOptions = networkService.getNetworkOptions(networkRequest); + // then + assertNotNull(networkOptions); + } + + @Test + void shouldHaveCorrectErrors() throws IOException { + // given + NetworkRequest networkRequest = getNetworkRequest(); + // when + NetworkOptionsResponse networkOptions = networkService.getNetworkOptions(networkRequest); + // then + assertEquals(getErrors(), networkOptions.getAllow().getErrors()); + } + + @Test + void shouldHaveCorrectCallMethods() throws IOException { + // given + NetworkRequest networkRequest = getNetworkRequest(); + // when + NetworkOptionsResponse networkOptions = networkService.getNetworkOptions(networkRequest); + // then + assertEquals(2, networkOptions.getAllow().getCallMethods().size()); + assertEquals("get_parse_error_blocks", networkOptions.getAllow().getCallMethods().get(0)); + assertEquals("mark_parse_error_block_checked", networkOptions.getAllow().getCallMethods().get(1)); + } + } + + @Nested + class NetworkStatusTest { + + @Test + void shouldReturnNetworkStatus() throws IOException { + // given + NetworkRequest networkRequest = getNetworkRequest(); + // when + NetworkStatusResponse networkStatus = networkService.getNetworkStatus(networkRequest); + // then + assertNotNull(networkStatus); + } + + @Test + void shouldHaveCorrectGenesisBlock() throws IOException { + // given + NetworkRequest networkRequest = getNetworkRequest(); + // when + NetworkStatusResponse networkStatus = networkService.getNetworkStatus(networkRequest); + // then + assertEquals(0, networkStatus.getGenesisBlockIdentifier().getIndex()); + assertEquals("Genesis", networkStatus.getGenesisBlockIdentifier().getHash()); + } + } + + @Nested + class NetworkListTest { + + @Test + void shouldReturnNetworkList() { + // given + MetadataRequest metadataRequest = EntityGenerator.givenMetadataRequest(); + // when + NetworkListResponse networkList = networkService.getNetworkList(metadataRequest); + // then + assertNotNull(networkList); + } + } } diff --git a/config/monitoring/dashboards/rosetta-dashboards-k8s.json b/config/monitoring/dashboards/rosetta-dashboards-k8s.json new file mode 100644 index 0000000000..18edddefe5 --- /dev/null +++ b/config/monitoring/dashboards/rosetta-dashboards-k8s.json @@ -0,0 +1,3464 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Rosetta-java monitoring", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 14, + "links": [], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 34, + "title": "Api Container Statistics", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "decimals": 1, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 2, + "x": 0, + "y": 1 + }, + "id": 52, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "process_uptime_seconds{application=\"api\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "", + "metric": "", + "range": true, + "refId": "A", + "step": 14400 + } + ], + "title": "Uptime", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "decimals": 1, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(50, 172, 45, 0.97)" + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 5, + "x": 2, + "y": 1 + }, + "id": 62, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "sum(jvm_memory_used_bytes{application=\"api\"})", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "range": true, + "refId": "A", + "step": 14400 + } + ], + "title": "JVM memory used", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 7, + "y": 1 + }, + "id": 44, + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "hikaricp_connections_max{application=\"api\",pool=\"CardanoRosettaJavaDBPool\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Connections Size", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 10, + "y": 1 + }, + "id": 63, + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "hikaricp_connections_active{application=\"api\",pool=\"CardanoRosettaJavaDBPool\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Active Connections", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 13, + "y": 1 + }, + "id": 64, + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "hikaricp_connections_idle{application=\"api\",pool=\"CardanoRosettaJavaDBPool\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Idle Connections", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 16, + "y": 1 + }, + "id": 46, + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "hikaricp_connections_timeout_total{application=\"api\", pool=\"CardanoRosettaJavaDBPool\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Connection Timeout Count", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 19, + "x": 0, + "y": 37 + }, + "id": 36, + "options": { + "dataLinks": [], + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max", + "min" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "hikaricp_connections_active{application=\"api\", pool=\"CardanoRosettaJavaDBPool\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Active", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "hikaricp_connections_idle{application=\"api\", pool=\"CardanoRosettaJavaDBPool\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Idle", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "hikaricp_connections_pending{application=\"api\", pool=\"CardanoRosettaJavaDBPool\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Pending", + "range": true, + "refId": "C" + } + ], + "title": "Connections", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 19, + "x": 0, + "y": 46 + }, + "id": 104, + "options": { + "legend": { + "calcs": [ + "max", + "min" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "irate(hikaricp_connections_usage_seconds_sum{application=\"api\",pool=\"CardanoRosettaJavaDBPool\"}[5m]) / irate(hikaricp_connections_usage_seconds_count{application=\"api\",pool=\"CardanoRosettaJavaDBPool\"}[5m])", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Usage time", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "irate(hikaricp_connections_creation_seconds_sum{application=\"api\",pool=\"CardanoRosettaJavaDBPool\"}[5m]) / \r\nirate(hikaricp_connections_creation_seconds_count{application=\"api\",pool=\"CardanoRosettaJavaDBPool\"}[5m])", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Creation time", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "irate(hikaricp_connections_acquire_seconds_sum{application=\"api\",pool=\"CardanoRosettaJavaDBPool\"}[5m]) / irate(hikaricp_connections_acquire_seconds_count{application=\"api\",pool=\"CardanoRosettaJavaDBPool\"}[5m])", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Acquire time", + "range": true, + "refId": "B" + } + ], + "title": "HikariCP Connections Time", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 1 + }, + "id": 65, + "title": "Yaci-Indexer Container Statistics", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "decimals": 1, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 2, + "x": 0, + "y": 2 + }, + "id": 66, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "process_uptime_seconds{application=\"yaci_indexer\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "", + "metric": "", + "range": true, + "refId": "A", + "step": 14400 + } + ], + "title": "Uptime", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "decimals": 1, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(50, 172, 45, 0.97)" + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 5, + "x": 2, + "y": 2 + }, + "id": 67, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "sum(jvm_memory_used_bytes{application=\"yaci_indexer\"})", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "range": true, + "refId": "A", + "step": 14400 + } + ], + "title": "JVM memory used", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 7, + "y": 2 + }, + "id": 69, + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "hikaricp_connections_max{application=\"yaci_indexer\", pool=\"HikariPool-1\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Connections Max", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 10, + "y": 2 + }, + "id": 103, + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "hikaricp_connections_active{application=\"yaci_indexer\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Active Connections", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 13, + "y": 2 + }, + "id": 71, + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "hikaricp_connections_min{application=\"yaci_indexer\", pool=\"HikariPool-1\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Connections Min", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 16, + "y": 2 + }, + "id": 72, + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "hikaricp_connections_timeout_total{application=\"yaci_indexer\", pool=\"HikariPool-1\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Connection Timeout Count", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 19, + "x": 0, + "y": 38 + }, + "id": 73, + "options": { + "dataLinks": [], + "legend": { + "calcs": [ + "max", + "min" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "hikaricp_connections_active{application=\"yaci_indexer\", pool=\"HikariPool-1\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Active", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "hikaricp_connections_idle{application=\"yaci_indexer\", pool=\"HikariPool-1\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Idle", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "hikaricp_connections_pending{application=\"yaci_indexer\", pool=\"HikariPool-1\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Pending", + "range": true, + "refId": "C" + } + ], + "title": "HikariCP Connections", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 19, + "x": 0, + "y": 46 + }, + "id": 16, + "options": { + "legend": { + "calcs": [ + "max", + "min" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "irate(hikaricp_connections_usage_seconds_sum{application=\"yaci_indexer\",pool=\"HikariPool-1\"}[5m]) / irate(hikaricp_connections_usage_seconds_count{application=\"yaci_indexer\",pool=\"HikariPool-1\"}[5m])", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Usage time", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "irate(hikaricp_connections_creation_seconds_sum{application=\"yaci_indexer\",pool=\"HikariPool-1\"}[5m]) / \r\nirate(hikaricp_connections_creation_seconds_count{application=\"yaci_indexer\",pool=\"HikariPool-1\"}[5m])", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Creation time", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "irate(hikaricp_connections_acquire_seconds_sum{application=\"yaci_indexer\"}[5m]) / irate(hikaricp_connections_acquire_seconds_count{application=\"yaci_indexer\"}[5m])", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Acquire time", + "range": true, + "refId": "B" + } + ], + "title": "HikariCP Connections Time", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 2 + }, + "id": 74, + "title": "Postgres Statistics", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "0": { + "color": "red", + "index": 0, + "text": "DOWN" + }, + "1": { + "color": "green", + "index": 1, + "text": "UP" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 0, + "y": 3 + }, + "id": 75, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "editorMode": "code", + "expr": "pg_up{application=\"postgreSQL_exporter\"}", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "postgreSQL Status", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 3, + "y": 3 + }, + "id": 76, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "editorMode": "code", + "expr": "pg_stat_database_deadlocks{application=\"postgreSQL_exporter\",datname=\"rosetta-java\"}", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "deadlocks", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 6, + "y": 3 + }, + "id": 77, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "editorMode": "code", + "expr": "pg_settings_max_connections{application=\"postgreSQL_exporter\"}", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "max_connects", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 9, + "y": 3 + }, + "id": 78, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "editorMode": "code", + "expr": "sum(pg_stat_activity_count) by (instance)", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "current_connections", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 12, + "y": 3 + }, + "id": 80, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "editorMode": "code", + "expr": "pg_settings_effective_io_concurrency{}", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "io_concurency", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 15, + "y": 3 + }, + "id": 81, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "editorMode": "code", + "expr": "pg_settings_shared_buffers_bytes{}", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "shared_buffer", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 0, + "y": 23 + }, + "id": 79, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "editorMode": "code", + "expr": "pg_database_size_bytes{application=\"postgreSQL_exporter\",datname=\"rosetta-java\"}", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "database_size", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 3, + "y": 23 + }, + "id": 84, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "editorMode": "code", + "expr": "pg_settings_max_parallel_workers{}", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "max_parallel_worker", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 6, + "y": 23 + }, + "id": 85, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "editorMode": "code", + "expr": "pg_settings_effective_cache_size_bytes{}", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "effective_cache", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 9, + "y": 23 + }, + "id": 86, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "editorMode": "code", + "expr": "pg_settings_max_wal_size_bytes{}", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "max_wal_size", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 12, + "y": 23 + }, + "id": 87, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "editorMode": "code", + "expr": "pg_settings_maintenance_work_mem_bytes{}", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "maintenance_work_mem", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 15, + "y": 23 + }, + "id": 82, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "editorMode": "code", + "expr": "pg_settings_work_mem_bytes{}", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "work_mem", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 8, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "sum(pg_stat_activity_count{application=\"postgreSQL_exporter\",datname=\"rosetta-java\",job=\"postgres_exporter and cardano_node\",server=\"db:5432\"})" + }, + "properties": [ + { + "id": "displayName", + "value": "Total active sessions" + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 18, + "x": 0, + "y": 26 + }, + "id": 83, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "editorMode": "code", + "expr": "sum(pg_stat_activity_count{application=\"postgreSQL_exporter\",datname=\"rosetta-java\"})", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "active_sessions", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 88, + "title": "Cardano node Statistics", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 0, + "y": 4 + }, + "id": 95, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "editorMode": "code", + "expr": "rts_gc_wall_ms{application=\"cardano_node\"}", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Uptime", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 3, + "y": 4 + }, + "id": 102, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "editorMode": "code", + "expr": "cardano_node_metrics_Mem_resident_int{application=\"cardano_node\"}", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Memory Usage", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "dark-red", + "value": 0 + }, + { + "color": "dark-green", + "value": 1 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 6, + "y": 4 + }, + "id": 93, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "cardano_node_metrics_connectionManager_incomingConns", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Incomming peers", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "dark-red", + "value": 0 + }, + { + "color": "dark-green", + "value": 1 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 9, + "y": 4 + }, + "id": 94, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "cardano_node_metrics_connectionManager_outgoingConns", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Outgoing peers", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "dark-red", + "value": 0 + }, + { + "color": "dark-green", + "value": 1 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 12, + "y": 4 + }, + "id": 91, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "cardano_node_metrics_epoch_int{application=\"cardano_node\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Epoch No", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "dark-red", + "value": 0 + }, + { + "color": "dark-green", + "value": 1 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 15, + "x": 0, + "y": 7 + }, + "id": 98, + "maxDataPoints": 100, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "cardano_node_metrics_blockNum_int{application=\"cardano_node\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Block Height", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "green", + "mode": "shades" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": -1, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 11, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "always", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "dark-red", + "value": 0 + }, + { + "color": "dark-green", + "value": 1 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 15, + "x": 0, + "y": 14 + }, + "id": 96, + "maxDataPoints": 100, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "hideZeros": false, + "maxWidth": 5, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "cardano_node_metrics_slotNum_int{application=\"cardano_node\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Slot Height", + "transparent": true, + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "dark-green", + "value": 5.6 + }, + { + "color": "#EAB839", + "value": 6 + }, + { + "color": "dark-red", + "value": 15 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 15, + "x": 0, + "y": 22 + }, + "id": 92, + "maxDataPoints": 100, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "cardano_node_metrics_density_real{application=\"cardano_node\"}*100", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Block Density", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 15, + "x": 0, + "y": 28 + }, + "id": 97, + "maxDataPoints": 100, + "options": { + "displayMode": "lcd", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "maxVizHeight": 300, + "minVizHeight": 16, + "minVizWidth": 8, + "namePlacement": "hidden", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "cardano_node_metrics_slotInEpoch_int{application=\"cardano_node\"}*10/43200", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Current Epoch Progress", + "type": "bargauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 1, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "dark-green", + "value": 1 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 15, + "x": 0, + "y": 30 + }, + "id": 99, + "maxDataPoints": 100, + "options": { + "displayMode": "lcd", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "maxVizHeight": 300, + "minVizHeight": 16, + "minVizWidth": 8, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "text" + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "cardano_node_metrics_blockfetchclient_blockdelay_cdfOne{application=\"cardano_node\"}*100", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "% Block Propagation <1s", + "type": "bargauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 1, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "dark-green", + "value": 1 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 15, + "x": 0, + "y": 32 + }, + "id": 100, + "maxDataPoints": 100, + "options": { + "displayMode": "lcd", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "maxVizHeight": 300, + "minVizHeight": 16, + "minVizWidth": 8, + "namePlacement": "hidden", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "cardano_node_metrics_blockfetchclient_blockdelay_cdfThree{application=\"cardano_node\"}*100", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "% Block Propagation <3s", + "type": "bargauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 1, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "dark-green", + "value": 1 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 15, + "x": 0, + "y": 34 + }, + "id": 101, + "maxDataPoints": 100, + "options": { + "displayMode": "lcd", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "maxVizHeight": 300, + "minVizHeight": 16, + "minVizWidth": 8, + "namePlacement": "hidden", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "DS_PROMETHEUS" + }, + "editorMode": "code", + "expr": "cardano_node_metrics_blockfetchclient_blockdelay_cdfFive{application=\"cardano_node\"}*100", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "% Block Propagation <5s", + "type": "bargauge" + } + ], + "preload": false, + "refresh": "5s", + "schemaVersion": 41, + "tags": [ + "node", + "postgres", + "indexer", + "api" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Rosetta Critical Operation Metrics", + "uid": "rosetta-java-001", + "version": 81 +} \ No newline at end of file diff --git a/docker-compose-indexer.yaml b/docker-compose-indexer.yaml index 8e3233851d..20ed770907 100644 --- a/docker-compose-indexer.yaml +++ b/docker-compose-indexer.yaml @@ -31,6 +31,18 @@ services: CONTINUE_PARSING_ON_ERROR: ${CONTINUE_PARSING_ON_ERROR} PEER_DISCOVERY: ${PEER_DISCOVERY} LOG: ${LOG} + + # DB performance tuning + INDEXER_DB_POOL_MIN_COUNT: ${INDEXER_DB_POOL_MIN_COUNT} + INDEXER_DB_POOL_MAX_COUNT: ${INDEXER_DB_POOL_MAX_COUNT} + INDEXER_DB_POOL_MAX_LIFETIME_MS: ${INDEXER_DB_POOL_MAX_LIFETIME_MS} + INDEXER_DB_POOL_CONNECTION_TIMEOUT_MS: ${INDEXER_DB_POOL_CONNECTION_TIMEOUT_MS} + INDEXER_DB_KEEP_ALIVE_MS: ${INDEXER_DB_KEEP_ALIVE_MS} + INDEXER_DB_LEAK_CONNECTIONS_WARNING_MS: ${INDEXER_DB_LEAK_CONNECTIONS_WARNING_MS} + INDEXER_DB_MONITOR_PERFORMANCE: ${INDEXER_DB_MONITOR_PERFORMANCE} + + # DB tuning debugging + INDEXER_DB_SHOW_SQL: ${INDEXER_DB_SHOW_SQL} volumes: - ${CARDANO_CONFIG}:/config - ${CARDANO_NODE_DIR}:${CARDANO_NODE_DIR} diff --git a/docker/dockerfiles/node/Dockerfile b/docker/dockerfiles/node/Dockerfile index cb7a1c42b6..45edd16224 100644 --- a/docker/dockerfiles/node/Dockerfile +++ b/docker/dockerfiles/node/Dockerfile @@ -154,6 +154,14 @@ ENV PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH RUN mkdir /logs +# Bake network config files (genesis, topology, etc.) into the image. +# This avoids hostPath / ConfigMap mounts in Kubernetes and ensures configs +# are versioned with the image. Update by rebuilding the image. +COPY ./config/node/mainnet /config/mainnet +COPY ./config/node/preprod /config/preprod +COPY ./config/node/preview /config/preview +COPY ./config/node/devkit /config/devkit + COPY ./docker/dockerfiles/node/wait-for-node-sync.sh /sbin/wait-for-node-sync.sh RUN chmod +x /sbin/wait-for-node-sync.sh diff --git a/docker/dockerfiles/postgres/entrypoint.sh b/docker/dockerfiles/postgres/entrypoint.sh index 62cf716aa2..5c5f9a6807 100644 --- a/docker/dockerfiles/postgres/entrypoint.sh +++ b/docker/dockerfiles/postgres/entrypoint.sh @@ -131,6 +131,9 @@ create_database_and_user() { sudo -u postgres "$PG_BIN/psql" -U postgres -p "$DB_PORT" -d "$DB_NAME" -c "GRANT ALL ON ALL TABLES IN SCHEMA public TO \"$DB_USER\";" > /dev/null sudo -u postgres "$PG_BIN/psql" -U postgres -p "$DB_PORT" -d "$DB_NAME" -c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO \"$DB_USER\";" > /dev/null + # Grant pg_monitor role so pg_exporter can access WAL/system stats (pg_ls_waldir etc.) + sudo -u postgres "$PG_BIN/psql" -U postgres -p "$DB_PORT" -c "GRANT pg_monitor TO \"$DB_USER\";" > /dev/null + echo "User configured" } diff --git a/docs/docs/development/monitoring_setup_guide.md b/docs/docs/development/monitoring_setup_guide.md index d24ff2b9c3..3fd8605877 100644 --- a/docs/docs/development/monitoring_setup_guide.md +++ b/docs/docs/development/monitoring_setup_guide.md @@ -1,66 +1,56 @@ -# Monitoring setup guide +# Monitoring Setup Guide -This guide explains : -- How to run the monitoring stack using Docker Compose -- Configure Prometheus as a data source in Grafa GUI. -- Import a Grafana dashboard from a JSON file. +This guide explains how to set up monitoring for **Docker Compose** deployments. --- -## 📦 1. Prerequisites +## Docker Compose -- The monitoring containers are used as services in rossetta-java so they are declared in the file `docker-compose.yaml`. By default, monitoring is enabled. You can disable it by commenting out the following line in your: +### Prerequisites - ```yaml - include: - - docker-compose-indexer.yaml - - docker-compose-node.yaml - - docker-compose-api.yaml - - docker-compose-monitor.yaml # uncomment this line if you want to disable monitoring - ``` +The monitoring containers are declared in `docker-compose-monitor.yaml`. By default, +monitoring is enabled. To disable it, comment out the relevant line: -- The following variables in `.env.docker-compose` must be set to appropriate values ​​and avoid port conflicts with other running services: +```yaml +include: + - docker-compose-indexer.yaml + - docker-compose-node.yaml + - docker-compose-api.yaml + - docker-compose-monitor.yaml # comment this line to disable monitoring +``` - ```dotenv - ## Monitoring port variables - PROMETHEUS_PORT=9090 - GRAFANA_PORT=3000 - POSTGRESQL_EXPORTER_PORT=9187 - ``` +Set monitoring ports in `.env.docker-compose`: ---- +```dotenv +## Monitoring port variables +PROMETHEUS_PORT=9090 +GRAFANA_PORT=3000 +POSTGRESQL_EXPORTER_PORT=9187 +``` -## 🚀 2. Start the Monitoring containers -Since `docker-compose-monitor.yaml` is included in the main `docker-compose.yaml`, the monitoring containers will automatically start when you bring up the Rosetta-java docker compose. These containers include: +### Start Monitoring -- `prometheus`: the backend metrics collector -- `grafana`: the dashboard visualizer -- `postgresql-exporter`: collects PostgreSQL metrics and exposes them to Prometheus +Since `docker-compose-monitor.yaml` is included in the main `docker-compose.yaml`, the +monitoring containers start automatically. These include: -You can verify services are running with: +- `prometheus` — metrics collector +- `grafana` — dashboard visualizer +- `postgresql-exporter` — PostgreSQL metrics exporter ```bash docker compose ps ``` ---- -## 🌐 3. Access Grafana - -Once services are running, open your browser and go to: +### Access Grafana (Docker Compose) ``` http://localhost: ``` -Replace `` with the actual port defined in your `.env` file (default: `3000`). - -**Default login credentials:** - -- **Username**: `admin` -- **Password**: `admin` (you'll be prompted to change this) +Default credentials: **admin** / **admin** (you will be prompted to change on first login). -After logging in, go to the Grafana menu → Dashboards → Rossetta-java-dashboards folder → Rosetta Critical Operation Metrics. +Navigate to: **Dashboards → Rosetta-java-dashboards → Rosetta Critical Operation Metrics** +The dashboard is pre-provisioned from `config/monitoring/dashboards/rosetta-dashboards.json` +and loads automatically when Grafana starts. -> **Note:** The dashboard is preloaded when the Grafana container is started, so no manual import is necessary. ---- \ No newline at end of file diff --git a/docs/docs/install-and-deploy/kubernetes/_category_.json b/docs/docs/install-and-deploy/kubernetes/_category_.json new file mode 100644 index 0000000000..55188cf54f --- /dev/null +++ b/docs/docs/install-and-deploy/kubernetes/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Kubernetes (Helm)", + "position": 3, + "link": { + "type": "generated-index", + "description": "Deploy Cardano Rosetta Java on Kubernetes using official Helm charts. Covers single-host K3s and managed cloud clusters (EKS, GKE, AKS)." + } +} diff --git a/docs/docs/install-and-deploy/kubernetes/deployment.md b/docs/docs/install-and-deploy/kubernetes/deployment.md new file mode 100644 index 0000000000..f70ffeb07d --- /dev/null +++ b/docs/docs/install-and-deploy/kubernetes/deployment.md @@ -0,0 +1,409 @@ +--- +sidebar_position: 2 +title: Deployment Runbook +description: This runbook covers deploying and operating **Cardano Rosetta Java** on Kubernetes (K3s single-host or managed cluster) for both **preprod** and **mainnet**. +--- +--- + +## Network Quick Reference + +| Parameter | Preprod | Mainnet | +|-----------|---------|---------| +| `global.network` | `preprod` | `mainnet` | +| `global.protocolMagic` | `1` | `764824073` | +| `global.profile` | `entry` | `mid` | +| Node storage | 100 Gi | 500 Gi | +| DB storage | 50 Gi | 200 Gi | +| Mithril snapshot size | ~5 GB | ~50 GB | +| Time to `LIVE` | ~2–3 hours | ~12–18 hours | + +--- + +## Prerequisites + +### Hardware + +| Resource | Preprod (entry) | Mainnet (mid) | +|----------|----------------|--------------| +| CPU | 4 cores | 8 cores | +| RAM | 32 GB | 48 GB | +| Disk | 150 GB SSD | 700 GB NVMe | +| OS | Ubuntu 22.04+ / Debian 12+ | same | + +> Use NVMe SSDs for PostgreSQL on mainnet. HDDs or standard SSDs cause serious performance degradation. + +### Software +- `kubectl` >= 1.28 +- `helm` >= 3.12 +- `jq` (for status checks) +- K3s or a managed K8s cluster (EKS, GKE, AKS) + +### Network +- Outbound TCP 3001 — cardano-node peer-to-peer (N2N) +- Outbound HTTPS — Mithril snapshot download + genesis key fetch from GitHub +- Inbound TCP 8082 — Rosetta API (if exposing externally) + +--- + +## Deployment + +### Deployment steps + +#### Step 1 — Clone and configure + +```bash +git clone https://github.com/cardano-foundation/cardano-rosetta-java.git +cd cardano-rosetta-java + +# Save password (needed for all future upgrades) +export DB_PASSWORD="$(openssl rand -base64 32)" +echo "DB_PASSWORD=${DB_PASSWORD}" > .env.k8s +chmod 600 .env.k8s +``` + +#### Step 2 — Pre-create the namespace + +```bash +kubectl create namespace cardano +``` + +> On re-deploy (namespace already exists), skip this step. + +#### Step 3 — Deploy + +> **Never use `--wait` or `--wait-for-jobs`** — the deployment takes hours (Mithril +> download + node sync + indexer sync). Helm would time out and roll back. + +The `index-applier` Job runs automatically as part of the release (default +`indexApplier.mode: automatic`). It waits for the Rosetta API to become ready, then +builds DB indexes in the background. No second `helm upgrade` is needed. + +**Preprod (K3s, entry profile):** +```bash +export DB_PASSWORD=$(grep DB_PASSWORD .env.k8s | cut -d= -f2-) +export KUBECONFIG=/etc/rancher/k3s/k3s.yaml + +helm upgrade --install rosetta helm/cardano-rosetta-java \ + -f helm/cardano-rosetta-java/values.yaml \ + -f helm/cardano-rosetta-java/values-preprod.yaml \ + -f helm/cardano-rosetta-java/values-k3s.yaml \ + --set global.profile=entry \ + "--set=global.db.password=${DB_PASSWORD}" \ + -n cardano 2>&1 | grep -v "walk.go" +``` + +**Mainnet (K3s, mid profile):** +```bash +export DB_PASSWORD=$(grep DB_PASSWORD .env.k8s | cut -d= -f2-) +export KUBECONFIG=/etc/rancher/k3s/k3s.yaml + +helm upgrade --install rosetta helm/cardano-rosetta-java \ + -f helm/cardano-rosetta-java/values.yaml \ + -f helm/cardano-rosetta-java/values-k3s.yaml \ + --set global.profile=mid \ + "--set=global.db.password=${DB_PASSWORD}" \ + -n cardano 2>&1 | grep -v "walk.go" +``` + +**Managed Kubernetes (EKS / GKE / AKS):** +```bash +helm upgrade --install rosetta helm/cardano-rosetta-java \ + -f helm/cardano-rosetta-java/values.yaml \ + --set global.network=mainnet \ + "--set=global.protocolMagic=764824073" \ + --set global.profile=mid \ + "--set=global.db.password=${DB_PASSWORD}" \ + --set global.storage.cardanoNode.storageClass="gp3" \ + --set global.storage.postgresql.storageClass="gp3" \ + -n cardano 2>&1 | grep -v "walk.go" +``` + +--- + +## Expected Timeline + +| Phase | Preprod | Mainnet | +|-------|---------|---------| +| Mithril snapshot download | ~30 min | 2–4 hours | +| Cardano node to tip | ~30 min | 15–60 min | +| PostgreSQL startup | ~1 min | ~1 min | +| yaci-indexer SYNCING | ~30 min | 2–8 hours | +| APPLYING_INDEXES (index-applier Job) | ~1–2 hours | ~6 hours | +| **Total to LIVE** | **~2–3 hours** | **~12–18 hours** | + +--- + +## Deployment Phases + +### Phase 1 — Mithril Snapshot Download + +Mithril runs as an init container (`mithril-download`) inside the `cardano-node` pod. +It fetches the aggregator endpoint and verification keys automatically from GitHub based +on `NETWORK` (same behaviour as Docker Compose — no manual key configuration required). +If a node DB already exists on the PVC, the download is skipped automatically. + +The `cardano-node` pod stays in `Init:0/1` until the init container completes. + +```bash +# Watch download progress +kubectl logs -f rosetta-cardano-node-0 -c mithril-download -n cardano +``` + +### Phase 2 — Cardano Node Sync + +After Mithril completes, `cardano-node` starts and syncs remaining blocks to tip. +After a machine reboot, the node re-validates all ImmutableDB chunks (~90 min for mainnet) +before the startup probe passes — this is normal. + +```bash +kubectl logs -f statefulset/rosetta-cardano-node -c cardano-node -n cardano +kubectl get pods -n cardano -w +``` + +### Phase 3 — PostgreSQL Startup + +PostgreSQL starts as soon as its PVC is available — it has no dependency on cardano-node. +While the node is syncing, PostgreSQL initialises its data directory and schema migrations +run immediately. This means yaci-indexer can begin connecting and indexing as soon as the +node's socat bridge (TCP port 3002) is reachable, without waiting hours for full sync. + +```bash +kubectl logs -f statefulset/rosetta-postgresql -n cardano +``` + +### Phase 4 — yaci-Indexer Syncing + +```bash +kubectl logs -f deployment/rosetta-yaci-indexer -n cardano +# Look for: Block No: XXXXXXX , Era: Babbage/Conway +``` + +Occasional `Connection reset` + immediate reconnect in logs is normal — it's the socat +TCP bridge timing out on idle and Yaci reconnecting in milliseconds. + +### Phase 5 — Rosetta API + +```bash +# Port-forward (local access) +kubectl port-forward svc/rosetta-rosetta-api 8082:8082 -n cardano + +# Port-forward (remote machine access) +kubectl port-forward --address 0.0.0.0 svc/rosetta-rosetta-api 8082:8082 -n cardano & + +# Check sync stage +curl -s -X POST http://localhost:8082/network/status \ + -H "Content-Type: application/json" \ + -d '{"network_identifier":{"blockchain":"cardano","network":"preprod"},"metadata":{}}' \ + | jq '{stage: .sync_status.stage, block: .current_block_identifier.index}' +``` + +Stages: +- `SYNCING` — yaci-indexer still catching up +- `APPLYING_INDEXES` — indexer reached tip; index-applier Job should now be triggered +- `LIVE` — fully operational + +### Phase 6 — Index Applier + +The index-applier Job is deployed automatically as part of the release +(`indexApplier.mode: automatic` default). It waits for the Rosetta API to respond, then +builds optimised database indexes. This Job takes **1–2 hours on preprod** and **~6 hours +on mainnet**. The Job is auto-cleaned up 24 hours after completion. + +```bash +# Monitor progress +kubectl logs -f job/rosetta-index-applier -n cardano +``` + +:::note +Operators who prefer explicit, operator-triggered indexing can use `indexApplier.mode: hook` +and trigger with a standard `helm upgrade` (without additional flags). See +[Helm Values Reference](./helm-values#index-applier) for details. +::: + +--- + +## Verification + +```bash +# All pods Running or Completed +kubectl get pods -n cardano + +# Resource usage +kubectl top pods -n cardano + +# API live check +curl -s -X POST http://localhost:8082/network/status \ + -H "Content-Type: application/json" \ + -d '{"network_identifier":{"blockchain":"cardano","network":"preprod"},"metadata":{}}' \ + | jq '.sync_status.stage' +# Expected: "LIVE" +``` + +--- + +## Common Operations + +### View logs +```bash +kubectl logs -f deployment/rosetta-rosetta-api -n cardano # Rosetta API +kubectl logs -f deployment/rosetta-yaci-indexer -n cardano # yaci-indexer +kubectl logs -f statefulset/rosetta-cardano-node -c cardano-node -n cardano # Cardano node +kubectl logs -f statefulset/rosetta-postgresql -n cardano # PostgreSQL +``` + +### Port-forward services +```bash +# Local access only +kubectl port-forward svc/rosetta-rosetta-api 8082:8082 -n cardano & + +# Remote access — bind to all interfaces +kubectl port-forward --address 0.0.0.0 svc/rosetta-rosetta-api 8082:8082 -n cardano & +``` + +### Upgrade to a new release +```bash +export DB_PASSWORD=$(grep DB_PASSWORD .env.k8s | cut -d= -f2-) + +helm upgrade rosetta helm/cardano-rosetta-java \ + -f helm/cardano-rosetta-java/values.yaml \ + -f helm/cardano-rosetta-java/values-k3s.yaml \ + "--set=global.db.password=${DB_PASSWORD}" \ + --set global.releaseVersion="2.1.0" \ + -n cardano 2>&1 | grep -v "walk.go" +``` + +### Restart a component +```bash +kubectl rollout restart deployment/rosetta-rosetta-api -n cardano +kubectl rollout restart deployment/rosetta-yaci-indexer -n cardano +kubectl rollout restart statefulset/rosetta-cardano-node -n cardano +``` + +### Scale API replicas +```bash +kubectl scale deployment rosetta-rosetta-api --replicas=2 -n cardano +``` + +### Teardown (preserve blockchain data) +```bash +helm uninstall rosetta -n cardano --no-hooks +# StatefulSet volumeClaimTemplates PVCs persist — delete manually if needed +``` + +### Full reset (delete all data — IRREVERSIBLE) +```bash +helm uninstall rosetta -n cardano --no-hooks +kubectl delete pvc --all -n cardano +``` + +--- + +## Security (Mainnet Checklist) + +- [ ] **Rotate DB password** — never use the default. Use `openssl rand -base64 32`. +- [ ] **Restrict API access** — use a LoadBalancer security group or Ingress with allowlisted IPs. +- [ ] **Disable debug endpoints** — ensure `PRINT_EXCEPTION=false` in production. +- [ ] **Secrets management** — use Sealed Secrets or Vault instead of `--set global.db.password`. +- [ ] **Network Policy** — restrict pod-to-pod traffic. +- [ ] **Token Registry** — review `rosetta-api.env.tokenRegistryEnabled`; requires outbound internet. + +--- + +## Troubleshooting + +| Symptom | Likely Cause | Resolution | +|---------|-------------|------------| +| `cardano-node` stuck in `Init:0/1` | Mithril download still running | `kubectl logs rosetta-cardano-node-0 -c mithril-download -n cardano` | +| `postgresql` stuck in `Init:0/1` | PVC not bound | `kubectl get pvc -n cardano`; check StorageClass | +| `yaci-indexer` stuck in `Init:1/3` | cardano-node socat bridge not up yet | `kubectl logs -c wait-for-node-tcp -n cardano` | +| `yaci-indexer` or `rosetta-api` stuck in `Init:2/3` | `copy-node-config` init container failed | `kubectl logs -c copy-node-config -n cardano` | +| Mithril: `signature error: Verification equation was not satisfied` | Empty verification key passed as env var | Upgrade chart ≥ 2.0.0 — keys are now auto-fetched by entrypoint | +| `yaci-indexer` CrashLoopBackOff (HikariCP timeout) | DB connection pool exhausted | Increase `hikariMaxPoolSize` in values (default 40) | +| `yaci-indexer` two pods simultaneously | Normal rolling update behaviour | Wait — old pod terminates once new one is ready | +| OOMKilled | Insufficient memory | Use a higher profile (`mid` or `advanced`) | +| PVC `Pending` | No suitable StorageClass | `kubectl get sc`; ensure `local-path` provisioner is running | +| `ImagePullBackOff` | Wrong image tag | Check `global.releaseVersion` in values | +| Port-forward not reachable from remote | Bound to 127.0.0.1 | Add `--address 0.0.0.0` to `kubectl port-forward` | +| cardano-node restart loop after reboot | Startup probe too short for ImmutableDB validation | `startupProbe.failureThreshold: 720` (3 hours); force-delete pod if StatefulSet update is blocked | + +--- + +## Resource Usage + +### Preprod — entry profile + +| Component | CPU req/limit | RAM req/limit | Storage | +|-----------|-------------|--------------|---------| +| cardano-node + sidecars | 1 / 2 CPU | 4 / 8 Gi | 100 Gi | +| postgresql | 1 / 2 CPU | 2 / 6 Gi | 50 Gi | +| yaci-indexer | 500m / 1 CPU | 1 / 2 Gi | — | +| rosetta-api | 250m / 1 CPU | 512Mi / 1 Gi | — | + +### Mainnet — mid profile + +| Component | CPU req/limit | RAM req/limit | Storage | +|-----------|-------------|--------------|---------| +| cardano-node + sidecars | 2 / 8 CPU | 12 / 24 Gi | 500 Gi | +| postgresql | 2 / 8 CPU | 16 / 32 Gi | 200 Gi | +| yaci-indexer | 1 / 4 CPU | 4 / 8 Gi | — | +| rosetta-api | 500m / 2 CPU | 2 / 4 Gi | — | +| **Total** | **~5.5 / 22 CPU** | **~34 / 68 Gi** | **700 Gi** | + +--- + +## Production Configuration (Mainnet) + +```yaml +# values-mainnet-prod.yaml +global: + profile: mid + network: mainnet + protocolMagic: "764824073" + +rosetta-api: + env: + tokenRegistryEnabled: true + tokenRegistryBaseUrl: "https://tokens.cardano.org/api" + tokenRegistryCacheTtlHours: 12 + removeSpentUtxos: true + removeSpentUtxosLastBlocksGraceCount: 129600 + + ingress: + enabled: true + className: nginx + host: rosetta.example.com + annotations: + nginx.ingress.kubernetes.io/proxy-body-size: "0" + cert-manager.io/cluster-issuer: letsencrypt-prod + tls: + - hosts: [rosetta.example.com] + secretName: rosetta-tls +``` + +--- + +## Disaster Recovery + +### Backup PVCs + +```bash +kubectl get pvc -n cardano + +# Use Velero for cluster-level backup: +# velero backup create rosetta-backup --include-namespaces cardano +``` + +### Re-sync from Mithril (if node DB is corrupt) + +```bash +# 1. Delete the node data PVC (triggers fresh Mithril download on next deploy) +kubectl delete pvc node-data-rosetta-cardano-node-0 -n cardano + +# 2. Re-deploy (mithril-download init container runs automatically) +export DB_PASSWORD=$(grep DB_PASSWORD .env.k8s | cut -d= -f2-) +helm upgrade rosetta helm/cardano-rosetta-java \ + -f helm/cardano-rosetta-java/values.yaml \ + -f helm/cardano-rosetta-java/values-k3s.yaml \ + "--set=global.db.password=${DB_PASSWORD}" \ + -n cardano 2>&1 | grep -v "walk.go" +``` diff --git a/docs/docs/install-and-deploy/kubernetes/helm-values.md b/docs/docs/install-and-deploy/kubernetes/helm-values.md new file mode 100644 index 0000000000..77b9bf886e --- /dev/null +++ b/docs/docs/install-and-deploy/kubernetes/helm-values.md @@ -0,0 +1,221 @@ +--- +sidebar_position: 3 +title: Helm Values Reference +description: Complete reference for all Helm chart values, with Docker Compose equivalents. +--- + +# Helm Values Reference + +This page documents all configurable values in the Cardano Rosetta Java Helm charts. +For each value, the equivalent Docker Compose environment variable is shown where applicable. + +## Global Values + +These values are shared across all subcharts via `global.*`. + +| Value | Default | Docker Compose equivalent | Description | +|-------|---------|--------------------------|-------------| +| `global.network` | `mainnet` | `NETWORK` | Blockchain network: `mainnet`, `preprod`, `preview` | +| `global.protocolMagic` | `"764824073"` | `PROTOCOL_MAGIC` | Cardano protocol magic number (always a quoted string to prevent scientific notation) | +| `global.releaseVersion` | `"2.1.0"` | `RELEASE_VERSION` | Docker image tag for API and indexer | +| `global.cardanoNodeVersion` | `"10.5.4"` | `CARDANO_NODE_VERSION` | Cardano node image tag | +| `global.pgVersionTag` | `REL_18_0` | `PG_VERSION_TAG` | PostgreSQL image tag | +| `global.mithrilVersion` | `2543.1-hotfix` | `MITHRIL_VERSION` | Mithril client image tag | +| `global.profile` | `mid` | — | Hardware profile: `entry`, `mid`, `advanced` | +| `global.sync` | `true` | `SYNC` | Set `false` for offline (API-only) mode | + +### Database (`global.db`) + +| Value | Default | Docker Compose equivalent | Description | +|-------|---------|--------------------------|-------------| +| `global.db.name` | `rosetta-java` | `DB_NAME` | PostgreSQL database name | +| `global.db.user` | `rosetta_db_admin` | `DB_USER` | PostgreSQL user | +| `global.db.password` | `""` **(required)** | `DB_SECRET` | PostgreSQL password — pass via `--set` or Sealed Secret | +| `global.db.schema` | `public` | `DB_SCHEMA` | PostgreSQL schema | +| `global.db.port` | `5432` | `DB_PORT` | PostgreSQL port | +| `global.db.host` | `""` | `DB_HOST` | Override to use external PostgreSQL. Empty = use in-cluster service | + +### Mithril (`global.mithril`) + +| Value | Default | Docker Compose equivalent | Description | +|-------|---------|--------------------------|-------------| +| `global.mithril.enabled` | `true` | `MITHRIL_SYNC` | Download Mithril snapshot on first install | +| `global.mithril.snapshotDigest` | `latest` | `SNAPSHOT_DIGEST` | Specific snapshot digest or `latest` | +| `global.mithril.aggregatorEndpoint` | `""` | `AGGREGATOR_ENDPOINT` | Custom aggregator URL. Empty = use network default | +| `global.mithril.genesisVerificationKey` | `""` | `GENESIS_VERIFICATION_KEY` | Custom genesis verification key | +| `global.mithril.ancillaryVerificationKey` | `""` | `ANCILLARY_VERIFICATION_KEY` | Custom ancillary verification key | + +### Storage (`global.storage`) + +| Value | Default | Docker Compose equivalent | Description | +|-------|---------|--------------------------|-------------| +| `global.storage.cardanoNode.size` | `500Gi` | `CARDANO_NODE_DIR` volume | PVC size for blockchain data | +| `global.storage.cardanoNode.storageClass` | `""` | — | StorageClass name. Empty = cluster default | +| `global.storage.postgresql.size` | `200Gi` | `DB_PATH` volume | PVC size for PostgreSQL data | +| `global.storage.postgresql.storageClass` | `""` | — | StorageClass name. Empty = cluster default | + +--- + +## Hardware Profiles + +Set `global.profile` to one of: `entry`, `mid`, `advanced`. + +### Resource Requests / Limits + +| Profile | cardano-node | postgresql | yaci-indexer | rosetta-api | +|---------|-------------|-----------|-------------|------------| +| `entry` req | 1 CPU / 6 Gi | 1 CPU / 6 Gi | 500m / 2 Gi | 250m / 1 Gi | +| `entry` lim | 4 CPU / 12 Gi | 4 CPU / 12 Gi | 2 CPU / 4 Gi | 1 CPU / 2 Gi | +| `mid` req | 2 CPU / 12 Gi | 2 CPU / 12 Gi | 1 CPU / 4 Gi | 500m / 2 Gi | +| `mid` lim | 8 CPU / 24 Gi | 8 CPU / 24 Gi | 4 CPU / 8 Gi | 2 CPU / 4 Gi | +| `advanced` req | 4 CPU / 24 Gi | 4 CPU / 24 Gi | 2 CPU / 8 Gi | 1 CPU / 4 Gi | +| `advanced` lim | 16 CPU / 48 Gi | 16 CPU / 48 Gi | 8 CPU / 16 Gi | 4 CPU / 8 Gi | + +### PostgreSQL Tuning (per profile) + +| Parameter | `entry` | `mid` | `advanced` | Docker Compose env | +|-----------|---------|-------|------------|-------------------| +| `maxConnections` | 120 | 300 | 600 | `DB_POSTGRES_MAX_CONNECTIONS` | +| `sharedBuffers` | 1GB | 4GB | 32GB | `DB_POSTGRES_SHARED_BUFFERS` | +| `effectiveCacheSize` | 2GB | 8GB | 32GB | `DB_POSTGRES_EFFECTIVE_CACHE_SIZE` | +| `workMem` | 16MB | 64MB | 96MB | `DB_POSTGRES_WORK_MEM` | +| API `poolMin` | 12 | 150 | 100 | `API_DB_POOL_MIN_COUNT` | +| API `poolMax` | 12 | 150 | 550 | `API_DB_POOL_MAX_COUNT` | + +--- + +## yaci-indexer Values + +| Value | Default | Docker Compose equivalent | Description | +|-------|---------|--------------------------|-------------| +| `yaci-indexer.env.removeSpentUtxos` | `true` | `REMOVE_SPENT_UTXOS` | Prune spent UTXOs to save disk | +| `yaci-indexer.env.removeSpentUtxosLastBlocksGraceCount` | `129600` | `REMOVE_SPENT_UTXOS_LAST_BLOCKS_GRACE_COUNT` | Blocks to retain (~30 days) | +| `yaci-indexer.env.removeSpentUtxosBatchSize` | `3000` | `REMOVE_SPENT_UTXOS_BATCH_SIZE` | Pruning batch size | +| `yaci-indexer.env.blockTransactionApiTimeoutSecs` | `120` | `BLOCK_TRANSACTION_API_TIMEOUT_SECS` | Timeout for block/tx API calls | +| `yaci-indexer.env.searchLimit` | `100` | `SEARCH_LIMIT` | Maximum search results | +| `yaci-indexer.env.continueParsingOnError` | `true` | `CONTINUE_PARSING_ON_ERROR` | Continue syncing on parse errors | +| `yaci-indexer.env.peerDiscovery` | `false` | `PEER_DISCOVERY` | Enable peer discovery | +| `yaci-indexer.env.logLevel` | `error` | `LOG` | Log level: `error`, `info`, `debug` | + +--- + +## rosetta-api Values + +| Value | Default | Docker Compose equivalent | Description | +|-------|---------|--------------------------|-------------| +| `rosetta-api.replicaCount` | `1` | — | Number of API replicas | +| `rosetta-api.env.httpConnectTimeoutSeconds` | `5` | `HTTP_CONNECT_TIMEOUT_SECONDS` | HTTP client connect timeout | +| `rosetta-api.env.httpRequestTimeoutSeconds` | `5` | `HTTP_REQUEST_TIMEOUT_SECONDS` | HTTP client request timeout | +| `rosetta-api.env.syncGraceSlotsCount` | `100` | `SYNC_GRACE_SLOTS_COUNT` | Slots behind tip before reporting out-of-sync | +| `rosetta-api.env.removeSpentUtxos` | `true` | `REMOVE_SPENT_UTXOS` | Must match yaci-indexer setting | +| `rosetta-api.env.removeSpentUtxosLastBlocksGraceCount` | `129600` | `REMOVE_SPENT_UTXOS_LAST_BLOCKS_GRACE_COUNT` | Must match yaci-indexer setting | +| `rosetta-api.env.blockTransactionApiTimeoutSecs` | `120` | `BLOCK_TRANSACTION_API_TIMEOUT_SECS` | Timeout for block queries | +| `rosetta-api.env.devkitEnabled` | `false` | `DEVKIT_ENABLED` | Enable DevKit integration | +| `rosetta-api.env.tokenRegistryEnabled` | `false` | `TOKEN_REGISTRY_ENABLED` | Enable Cardano token registry | +| `rosetta-api.env.tokenRegistryBaseUrl` | `""` | `TOKEN_REGISTRY_BASE_URL` | Token registry URL (leave empty = disabled) | +| `rosetta-api.env.tokenRegistryCacheTtlHours` | `12` | `TOKEN_REGISTRY_CACHE_TTL_HOURS` | Token metadata cache TTL | +| `rosetta-api.env.tokenRegistryLogoFetch` | `false` | `TOKEN_REGISTRY_LOGO_FETCH` | Fetch token logos (bandwidth-intensive) | +| `rosetta-api.env.tokenRegistryRequestTimeoutSeconds` | `2` | `TOKEN_REGISTRY_REQUEST_TIMEOUT_SECONDS` | Token registry request timeout | + +### Ingress + +```yaml +rosetta-api: + ingress: + enabled: true + className: nginx + host: rosetta.example.com + tls: + - secretName: rosetta-tls + hosts: + - rosetta.example.com +``` + +--- + +## Index Applier + +| Value | Default | Description | +|-------|---------|-------------| +| `indexApplier.enabled` | `true` | Deploy the index-applier Job | +| `indexApplier.mode` | `automatic` | `automatic`: plain Job (GitOps-friendly, no hooks). `hook`: legacy Helm post-install/post-upgrade hook. | +| `indexApplier.pollInterval` | `60` | Seconds between API readiness polls | + +In `automatic` mode the Job runs as part of the release (compatible with ArgoCD and `--no-hooks`). The Job is +cleaned up 24 hours after completion via `ttlSecondsAfterFinished`. + +In `hook` mode the Job is a Helm post-install/post-upgrade hook. Monitor it independently and never use +`--wait-for-jobs` as it can run for up to 18 hours on mainnet. + +--- + +## Subchart Toggles + +| Value | Default | Description | +|-------|---------|-------------| +| `cardano-node.enabled` | `true` | Deploy the Cardano node | +| `postgresql.enabled` | `true` | Deploy in-cluster PostgreSQL | +| `yaci-indexer.enabled` | `true` | Deploy the Yaci indexer | +| `rosetta-api.enabled` | `true` | Deploy the Rosetta API | + +--- + +## Using an External PostgreSQL + +To use an external managed database (RDS, Cloud SQL, etc.): + +```yaml +# values-external-db.yaml +postgresql: + enabled: false + +global: + db: + host: "my-rds-instance.cluster-xyz.us-east-1.rds.amazonaws.com" + name: "rosetta-java" + user: "rosetta_db_admin" + password: "" # pass via --set global.db.password=... +``` + +```bash +helm upgrade --install rosetta helm/cardano-rosetta-java \ + -f helm/cardano-rosetta-java/values.yaml \ + -f values-external-db.yaml \ + --set global.db.password="${DB_PASSWORD}" +``` + +--- + +## Example: Preprod on K3s (entry profile) + +```yaml +# values-preprod.yaml (already included in the chart) +global: + network: preprod + protocolMagic: "1" + profile: entry + storage: + cardanoNode: + size: 100Gi + storageClass: local-path + postgresql: + size: 50Gi + storageClass: local-path + +yaci-indexer: + env: + removeSpentUtxos: false + peerDiscovery: true + +rosetta-api: + env: + removeSpentUtxos: false +``` + +```bash +helm upgrade --install rosetta helm/cardano-rosetta-java \ + -f helm/cardano-rosetta-java/values-k3s.yaml \ + -f helm/cardano-rosetta-java/values-preprod.yaml \ + --set global.db.password="${DB_PASSWORD}" \ + -n cardano +``` diff --git a/docs/docs/install-and-deploy/kubernetes/overview.md b/docs/docs/install-and-deploy/kubernetes/overview.md new file mode 100644 index 0000000000..f7c63ec829 --- /dev/null +++ b/docs/docs/install-and-deploy/kubernetes/overview.md @@ -0,0 +1,115 @@ +--- +sidebar_position: 1 +title: Overview +description: Kubernetes deployment overview — when to use it, architecture, and prerequisites. +--- + +# Kubernetes Deployment Overview + +Cardano Rosetta Java ships official [Helm charts](https://helm.sh/) for Kubernetes +deployment. The charts support both **single-host K3s** deployments and any **managed +Kubernetes cluster** (EKS, GKE, AKS). + +## Architecture + +The deployment consists of **4 Helm subcharts** plus orchestration resources in the +parent chart: + +``` +Parent chart: cardano-rosetta-java +├── cardano-node StatefulSet — Cardano blockchain node + socat sidecar +├── postgresql StatefulSet — PostgreSQL with blockchain-tuned configuration +├── yaci-indexer Deployment — Blockchain data indexer (Yaci Store) +└── rosetta-api Deployment — Rosetta HTTP API +``` + +Monitoring (Prometheus, Grafana) is **not included** — production clusters should bring their own monitoring solution. + +### Key Architectural Differences from Docker Compose + +**UNIX socket bridging via socat** + +Docker Compose shares the Cardano node's UNIX socket (`/node/node.socket`) via volume +mounts. In Kubernetes, pods cannot share UNIX sockets. A `socat` sidecar inside the +`cardano-node` pod forwards TCP connections on port `3002` to the UNIX socket. The +`yaci-indexer` connects using the `n2c-socat` Spring profile. + +**Startup ordering via init containers** + +Docker Compose `depends_on` chains are replaced by Kubernetes init containers: + +``` +cardano-node (init: mithril-download → cardano-node starts) + │ + ├─ postgresql starts immediately (no node dependency — it's just a database) + │ + └─ yaci-indexer (wait-for-postgres: pg_isready + wait-for-node-tcp: nc cardano-node:3002 + copy-node-config: copies configs from node image) + rosetta-api (wait-for-postgres + wait-for-indexer: /actuator/health + copy-node-config: copies configs from node image) + index-applier Job ──────────────► (plain Job, runs automatically with the release) +``` + +The `cardano-node` pod runs **three containers**: the node itself, a `socat` sidecar +(bridges the UNIX socket to TCP port 3002), and `cardano-submit-api` — so `READY` shows +`3/3` when fully up. + +The `index-applier` is a plain Kubernetes Job that runs automatically with the release +(default `indexApplier.mode: automatic`). It waits for the Rosetta API to become +responsive, then builds optimised database indexes. This Job can take **6–18 hours** on +mainnet. It is cleaned up automatically after 24 hours via `ttlSecondsAfterFinished`. + +:::note +The `index-applier` runs as a plain Job by default — no `--no-hooks` flag is needed. +Operators who prefer explicit, operator-triggered index building can switch to legacy hook +mode with `--set indexApplier.mode=hook`. In hook mode, monitor the Job independently and +never use `--wait-for-jobs`. +::: + +**Three sync stages** + +| Stage | What's happening | Pods ready | +|---|---|---| +| `SYNCING` | yaci-indexer catching up to chain tip | All pods up, API responding | +| `APPLYING_INDEXES` | Indexer reached tip, DB indexes being built | All pods up, API responding | +| `LIVE` | Fully synced, all indexes valid | All pods ready, API fully functional | + +## Hardware Profiles + +Three built-in profiles scale resources across all components: + +| Profile | Total RAM | Total vCPU | Use case | +|---|---|---|---| +| `entry` | 32 GB | 4 cores | Preprod, single host, K3s | +| `mid` | 48 GB | 8 cores | Mainnet production | +| `advanced` | 94 GB | 16 cores | High-throughput production | + +See [Helm Values Reference](./helm-values) for per-component breakdown. + +## Prerequisites + +### Software +- **Helm** v3.14+ (`helm version`) +- **kubectl** configured for your cluster +- **K3s** v1.28+ or any managed Kubernetes cluster (EKS, GKE, AKS) + +### Access +- A `DB_PASSWORD` for PostgreSQL (min 16 characters recommended for production) +- Outbound internet access (Mithril snapshot download, ~20 GB for mainnet) + +### Hardware (minimum for preprod / `entry` profile) +- 8 vCPU +- 32 GB RAM +- 150 GB fast SSD (for node data + PostgreSQL) + +### Hardware (recommended for mainnet / `mid` profile) +- 16 vCPU +- 64 GB RAM +- 700 GB fast SSD (500 GB node + 200 GB PostgreSQL) + +## Next Steps + +- [Deployment Runbook](./deployment) — step-by-step deploy for preprod and mainnet +- [Helm Values Reference](./helm-values) — full configuration reference diff --git a/helm/cardano-rosetta-java/.helmignore b/helm/cardano-rosetta-java/.helmignore new file mode 100644 index 0000000000..b511531abe --- /dev/null +++ b/helm/cardano-rosetta-java/.helmignore @@ -0,0 +1,18 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. + +# Common VCS dirs +.git/ +.gitignore +.gitmodules + +# Common backup files +*.swp +*.bak +*.tmp +*~ + +# OS files +.DS_Store + diff --git a/helm/cardano-rosetta-java/Chart.yaml b/helm/cardano-rosetta-java/Chart.yaml new file mode 100644 index 0000000000..2f016dfd3e --- /dev/null +++ b/helm/cardano-rosetta-java/Chart.yaml @@ -0,0 +1,28 @@ +apiVersion: v2 +name: cardano-rosetta-java +description: Cardano Rosetta Java — Rosetta/Mesh API implementation for the Cardano blockchain +type: application +version: 2.0.0 +appVersion: "2.0.0" +keywords: [cardano, rosetta, blockchain, mesh] +home: https://github.com/cardano-foundation/cardano-rosetta-java +maintainers: + - name: Cardano Foundation + url: https://cardanofoundation.org +dependencies: + - name: cardano-node + version: "2.0.0" + repository: "file://charts/cardano-node" + condition: cardano-node.enabled + - name: postgresql + version: "2.0.0" + repository: "file://charts/postgresql" + condition: postgresql.enabled + - name: yaci-indexer + version: "2.0.0" + repository: "file://charts/yaci-indexer" + condition: yaci-indexer.enabled + - name: rosetta-api + version: "2.0.0" + repository: "file://charts/rosetta-api" + condition: rosetta-api.enabled diff --git a/helm/cardano-rosetta-java/charts/cardano-node/Chart.yaml b/helm/cardano-rosetta-java/charts/cardano-node/Chart.yaml new file mode 100644 index 0000000000..75dc0ceaba --- /dev/null +++ b/helm/cardano-rosetta-java/charts/cardano-node/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: cardano-node +description: Cardano Node subchart for cardano-rosetta-java +type: application +version: 2.0.0 +appVersion: "2.0.0" diff --git a/helm/cardano-rosetta-java/charts/cardano-node/templates/_helpers.tpl b/helm/cardano-rosetta-java/charts/cardano-node/templates/_helpers.tpl new file mode 100644 index 0000000000..496f97cf05 --- /dev/null +++ b/helm/cardano-rosetta-java/charts/cardano-node/templates/_helpers.tpl @@ -0,0 +1,23 @@ +{{- define "cardano-node.fullname" -}} +{{- printf "%s-cardano-node" .Release.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "cardano-node.headlessName" -}} +{{- printf "%s-cardano-node-headless" .Release.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "cardano-node.commonLabels" -}} +app.kubernetes.io/name: cardano-node +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: {{ .Values.global.cardanoNodeVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{- define "cardano-node.selectorLabels" -}} +app: {{ include "cardano-node.fullname" . }} +component: cardano-node +{{- end }} + +{{- define "cardano-node.profile" -}} +{{- .Values.global.profile | default "mid" }} +{{- end }} diff --git a/helm/cardano-rosetta-java/charts/cardano-node/templates/service.yaml b/helm/cardano-rosetta-java/charts/cardano-node/templates/service.yaml new file mode 100644 index 0000000000..42b5ed16fa --- /dev/null +++ b/helm/cardano-rosetta-java/charts/cardano-node/templates/service.yaml @@ -0,0 +1,41 @@ +# Headless service for StatefulSet stable DNS +apiVersion: v1 +kind: Service +metadata: + name: {{ include "cardano-node.headlessName" . }} + labels: + {{- include "cardano-node.commonLabels" . | nindent 4 }} +spec: + clusterIP: None + selector: + {{- include "cardano-node.selectorLabels" . | nindent 4 }} + ports: + - name: node-to-node + port: 3001 + targetPort: 3001 +--- +# ClusterIP service for inter-pod communication +apiVersion: v1 +kind: Service +metadata: + name: {{ include "cardano-node.fullname" . }} + labels: + {{- include "cardano-node.commonLabels" . | nindent 4 }} + component: cardano-node +spec: + type: ClusterIP + selector: + {{- include "cardano-node.selectorLabels" . | nindent 4 }} + ports: + - name: node-to-node + port: 3001 + targetPort: 3001 + - name: socat-n2c + port: 3002 + targetPort: 3002 + - name: submit-api + port: 8090 + targetPort: 8090 + - name: metrics + port: 12798 + targetPort: 12798 diff --git a/helm/cardano-rosetta-java/charts/cardano-node/templates/statefulset.yaml b/helm/cardano-rosetta-java/charts/cardano-node/templates/statefulset.yaml new file mode 100644 index 0000000000..a03b7852f4 --- /dev/null +++ b/helm/cardano-rosetta-java/charts/cardano-node/templates/statefulset.yaml @@ -0,0 +1,175 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "cardano-node.fullname" . }} + labels: + {{- include "cardano-node.commonLabels" . | nindent 4 }} + component: cardano-node +spec: + replicas: 1 + serviceName: {{ include "cardano-node.headlessName" . }} + selector: + matchLabels: + {{- include "cardano-node.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "cardano-node.selectorLabels" . | nindent 8 }} + {{- include "cardano-node.commonLabels" . | nindent 8 }} + spec: + terminationGracePeriodSeconds: 60 + + {{- if .Values.global.mithril.enabled }} + initContainers: + - name: mithril-download + image: "cardanofoundation/cardano-rosetta-java-mithril:{{ .Values.global.mithrilVersion }}" + command: + - sh + - -c + - | + if [ -d /node/db ] && [ "$(ls -A /node/db 2>/dev/null)" ]; then + echo "Node DB already exists ($(du -sh /node/db 2>/dev/null | cut -f1)), skipping Mithril download" + exit 0 + fi + echo "No existing node DB found, starting Mithril download..." + exec /sbin/entrypoint.sh + env: + - name: NETWORK + value: {{ .Values.global.network | quote }} + - name: MITHRIL_SYNC + value: {{ .Values.global.mithril.sync | toString | quote }} + - name: SNAPSHOT_DIGEST + value: {{ .Values.global.mithril.snapshotDigest | quote }} + - name: AGGREGATOR_ENDPOINT + value: {{ .Values.global.mithril.aggregatorEndpoint | quote }} + - name: GENESIS_VERIFICATION_KEY + value: {{ .Values.global.mithril.genesisVerificationKey | quote }} + - name: ANCILLARY_VERIFICATION_KEY + value: {{ .Values.global.mithril.ancillaryVerificationKey | quote }} + volumeMounts: + - name: node-data + mountPath: /node + resources: + {{- $profile := (include "cardano-node.profile" .) }} + {{- $res := index .Values.global.profiles $profile "resources" "mithril" }} + {{- toYaml $res | nindent 12 }} + {{- end }} + + containers: + - name: cardano-node + image: "cardanofoundation/cardano-rosetta-java-cardano-node:{{ .Values.global.cardanoNodeVersion }}" + command: ["/sbin/entrypoint.sh", "cardano-node"] + env: + - name: CARDANO_NODE_SOCKET_PATH + value: /node/node.socket + - name: CARDANO_NODE_PORT + value: "3001" + - name: CARDANO_NODE_DB + value: /node/db + - name: CARDANO_CONFIG_CONTAINER_PATH + value: /config/{{ .Values.global.network }} + ports: + - name: node-to-node + containerPort: 3001 + protocol: TCP + - name: metrics + containerPort: 12798 + protocol: TCP + volumeMounts: + - name: node-data + mountPath: /node + resources: + {{- $profile := (include "cardano-node.profile" .) }} + {{- $res := index .Values.global.profiles $profile "resources" "cardanoNode" }} + {{- toYaml $res | nindent 12 }} + livenessProbe: + exec: + command: + - test + - -S + - /node/node.socket + initialDelaySeconds: 120 + periodSeconds: 30 + failureThreshold: 5 + timeoutSeconds: 5 + startupProbe: + exec: + command: + - test + - -S + - /node/node.socket + initialDelaySeconds: 60 + periodSeconds: 15 + failureThreshold: {{ (((.Values.node).startupProbe).failureThreshold) | default 1920 }} + timeoutSeconds: 5 + + - name: socat + image: alpine/socat:1.7.4.4-r0 + command: + - sh + - -c + - | + echo "Waiting for cardano-node socket at /node/node.socket..." + until [ -S /node/node.socket ]; do + echo "Socket not yet available, waiting 5s..." + sleep 5 + done + echo "Socket found, starting socat bridge TCP:3002 -> UNIX:/node/node.socket" + exec socat \ + TCP-LISTEN:3002,reuseaddr,fork \ + UNIX-CONNECT:/node/node.socket + ports: + - name: socat-n2c + containerPort: 3002 + protocol: TCP + volumeMounts: + - name: node-data + mountPath: /node + resources: + {{- toYaml .Values.global.initContainerResources | nindent 12 }} + + - name: cardano-submit-api + image: "cardanofoundation/cardano-rosetta-java-cardano-node:{{ .Values.global.cardanoNodeVersion }}" + command: ["/sbin/entrypoint.sh", "cardano-submit-api"] + env: + - name: NETWORK + value: {{ .Values.global.network | quote }} + - name: PROTOCOL_MAGIC + value: {{ .Values.global.protocolMagic | toString | quote }} + - name: CARDANO_NODE_SOCKET_PATH + value: /node/node.socket + - name: NODE_SUBMIT_API_PORT + value: "8090" + ports: + - name: submit-api + containerPort: 8090 + protocol: TCP + volumeMounts: + - name: node-data + mountPath: /node + resources: + {{- $profile := (include "cardano-node.profile" .) }} + {{- $res := index .Values.global.profiles $profile "resources" "submitApi" }} + {{- toYaml $res | nindent 12 }} + readinessProbe: + tcpSocket: + port: 8090 + initialDelaySeconds: 30 + periodSeconds: 15 + failureThreshold: 10 + + volumeClaimTemplates: + - metadata: + name: node-data + labels: + {{- include "cardano-node.commonLabels" . | nindent 10 }} + component: cardano-node + spec: + accessModes: + - ReadWriteOnce + {{- if .Values.global.storage.cardanoNode.storageClass }} + storageClassName: {{ .Values.global.storage.cardanoNode.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.global.storage.cardanoNode.size }} diff --git a/helm/cardano-rosetta-java/charts/cardano-node/values.yaml b/helm/cardano-rosetta-java/charts/cardano-node/values.yaml new file mode 100644 index 0000000000..ebadd9a816 --- /dev/null +++ b/helm/cardano-rosetta-java/charts/cardano-node/values.yaml @@ -0,0 +1,2 @@ +node: + enabled: true diff --git a/helm/cardano-rosetta-java/charts/postgresql/Chart.yaml b/helm/cardano-rosetta-java/charts/postgresql/Chart.yaml new file mode 100644 index 0000000000..eab1d2ab99 --- /dev/null +++ b/helm/cardano-rosetta-java/charts/postgresql/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: postgresql +description: PostgreSQL subchart for cardano-rosetta-java +type: application +version: 2.0.0 +appVersion: "2.0.0" diff --git a/helm/cardano-rosetta-java/charts/postgresql/templates/_helpers.tpl b/helm/cardano-rosetta-java/charts/postgresql/templates/_helpers.tpl new file mode 100644 index 0000000000..ac1ca81bb5 --- /dev/null +++ b/helm/cardano-rosetta-java/charts/postgresql/templates/_helpers.tpl @@ -0,0 +1,22 @@ +{{- define "postgresql.fullname" -}} +{{- printf "%s-postgresql" .Release.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "postgresql.headlessName" -}} +{{- printf "%s-postgresql-headless" .Release.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "postgresql.commonLabels" -}} +app.kubernetes.io/name: postgresql +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{- define "postgresql.selectorLabels" -}} +app: {{ include "postgresql.fullname" . }} +component: postgresql +{{- end }} + +{{- define "postgresql.profile" -}} +{{- .Values.global.profile | default "mid" }} +{{- end }} diff --git a/helm/cardano-rosetta-java/charts/postgresql/templates/secret.yaml b/helm/cardano-rosetta-java/charts/postgresql/templates/secret.yaml new file mode 100644 index 0000000000..88689f2296 --- /dev/null +++ b/helm/cardano-rosetta-java/charts/postgresql/templates/secret.yaml @@ -0,0 +1,13 @@ +{{- if not .Values.global.db.existingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "cardano-rosetta-java.dbSecretName" . }} + labels: + {{- include "postgresql.commonLabels" . | nindent 4 }} + annotations: + helm.sh/resource-policy: keep +type: Opaque +stringData: + db-secret: {{ required "global.db.password is required. Set it with --set global.db.password=" .Values.global.db.password | quote }} +{{- end }} diff --git a/helm/cardano-rosetta-java/charts/postgresql/templates/service.yaml b/helm/cardano-rosetta-java/charts/postgresql/templates/service.yaml new file mode 100644 index 0000000000..9ecf02f501 --- /dev/null +++ b/helm/cardano-rosetta-java/charts/postgresql/templates/service.yaml @@ -0,0 +1,31 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "postgresql.headlessName" . }} + labels: + {{- include "postgresql.commonLabels" . | nindent 4 }} +spec: + clusterIP: None + selector: + {{- include "postgresql.selectorLabels" . | nindent 4 }} + ports: + - name: postgres + port: {{ .Values.global.db.port }} + targetPort: {{ .Values.global.db.port }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "postgresql.fullname" . }} + labels: + {{- include "postgresql.commonLabels" . | nindent 4 }} + component: postgresql +spec: + type: ClusterIP + selector: + {{- include "postgresql.selectorLabels" . | nindent 4 }} + ports: + - name: postgres + port: {{ .Values.global.db.port }} + targetPort: {{ .Values.global.db.port }} + protocol: TCP diff --git a/helm/cardano-rosetta-java/charts/postgresql/templates/statefulset.yaml b/helm/cardano-rosetta-java/charts/postgresql/templates/statefulset.yaml new file mode 100644 index 0000000000..39ce61b8dc --- /dev/null +++ b/helm/cardano-rosetta-java/charts/postgresql/templates/statefulset.yaml @@ -0,0 +1,141 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "postgresql.fullname" . }} + labels: + {{- include "postgresql.commonLabels" . | nindent 4 }} + component: postgresql +spec: + replicas: 1 + serviceName: {{ include "postgresql.headlessName" . }} + selector: + matchLabels: + {{- include "postgresql.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "postgresql.selectorLabels" . | nindent 8 }} + {{- include "postgresql.commonLabels" . | nindent 8 }} + spec: + terminationGracePeriodSeconds: 60 + + containers: + - name: postgresql + image: "cardanofoundation/cardano-rosetta-java-postgres:{{ .Values.global.pgVersionTag }}" + env: + - name: DB_USER + value: {{ .Values.global.db.user | quote }} + - name: DB_NAME + value: {{ .Values.global.db.name | quote }} + - name: DB_PORT + value: {{ .Values.global.db.port | toString | quote }} + - name: DB_SCHEMA + value: {{ .Values.global.db.schema | quote }} + - name: NETWORK + value: {{ .Values.global.network | quote }} + - name: PGPASSWORD + value: "postgres" + - name: DB_SECRET + valueFrom: + secretKeyRef: + name: {{ include "cardano-rosetta-java.dbSecretName" . }} + key: db-secret + {{- $profile := (include "postgresql.profile" .) }} + {{- $db := index .Values.global.profiles $profile "db" }} + - name: DB_POSTGRES_MAX_CONNECTIONS + value: {{ $db.maxConnections | toString | quote }} + - name: DB_POSTGRES_SHARED_BUFFERS + value: {{ $db.sharedBuffers | quote }} + - name: DB_POSTGRES_EFFECTIVE_CACHE_SIZE + value: {{ $db.effectiveCacheSize | quote }} + - name: DB_POSTGRES_WORK_MEM + value: {{ $db.workMem | quote }} + - name: DB_POSTGRES_MAINTENANCE_WORK_MEM + value: {{ $db.maintenanceWorkMem | quote }} + - name: DB_POSTGRES_WAL_BUFFERS + value: {{ $db.walBuffers | quote }} + - name: DB_POSTGRES_CHECKPOINT_COMPLETION_TARGET + value: {{ $db.checkpointCompletionTarget | toString | quote }} + - name: DB_POSTGRES_RANDOM_PAGE_COST + value: {{ $db.randomPageCost | toString | quote }} + - name: DB_POSTGRES_EFFECTIVE_IO_CONCURRENCY + value: {{ $db.effectiveIoConcurrency | toString | quote }} + - name: DB_POSTGRES_PARALLEL_TUPLE_COST + value: {{ $db.parallelTupleCost | toString | quote }} + - name: DB_POSTGRES_PARALLEL_SETUP_COST + value: {{ $db.parallelSetupCost | toString | quote }} + - name: DB_POSTGRES_MAX_PARALLEL_WORKERS_PER_GATHER + value: {{ $db.maxParallelWorkersPerGather | toString | quote }} + - name: DB_POSTGRES_MAX_PARALLEL_WORKERS + value: {{ $db.maxParallelWorkers | toString | quote }} + - name: DB_POSTGRES_SEQ_PAGE_COST + value: {{ $db.seqPageCost | toString | quote }} + - name: DB_POSTGRES_JIT + value: {{ $db.jit | quote }} + - name: DB_POSTGRES_BGWRITER_LRU_MAXPAGES + value: {{ $db.bgwriterLruMaxpages | toString | quote }} + - name: DB_POSTGRES_BGWRITER_DELAY + value: {{ $db.bgwriterDelay | quote }} + {{- if $db.autovacuumMaxWorkers }} + - name: DB_POSTGRES_AUTOVACUUM_MAX_WORKERS + value: {{ $db.autovacuumMaxWorkers | toString | quote }} + {{- end }} + ports: + - name: postgres + containerPort: {{ .Values.global.db.port }} + protocol: TCP + volumeMounts: + - name: pg-data + mountPath: /var/lib/postgresql/data + - name: dshm + mountPath: /dev/shm + readinessProbe: + exec: + command: + - sh + - -c + - | + PGPASSWORD="${DB_SECRET}" pg_isready \ + -U {{ .Values.global.db.user | quote }} \ + -d {{ .Values.global.db.name | quote }} \ + -p {{ .Values.global.db.port }} \ + -h localhost + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 15 + timeoutSeconds: 5 + livenessProbe: + exec: + command: + - sh + - -c + - "pg_isready -U postgres -h localhost -p {{ .Values.global.db.port }}" + initialDelaySeconds: 60 + periodSeconds: 30 + failureThreshold: 5 + timeoutSeconds: 5 + resources: + {{- $res := index .Values.global.profiles $profile "resources" "postgresql" }} + {{- toYaml $res | nindent 12 }} + + volumes: + # SHM emulation: replaces Docker Compose shm_size: 4g + - name: dshm + emptyDir: + medium: Memory + sizeLimit: 4Gi + volumeClaimTemplates: + - metadata: + name: pg-data + labels: + {{- include "postgresql.commonLabels" . | nindent 10 }} + component: postgresql + spec: + accessModes: + - ReadWriteOnce + {{- if .Values.global.storage.postgresql.storageClass }} + storageClassName: {{ .Values.global.storage.postgresql.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.global.storage.postgresql.size }} diff --git a/helm/cardano-rosetta-java/charts/postgresql/values.yaml b/helm/cardano-rosetta-java/charts/postgresql/values.yaml new file mode 100644 index 0000000000..5ceec365f4 --- /dev/null +++ b/helm/cardano-rosetta-java/charts/postgresql/values.yaml @@ -0,0 +1,2 @@ +service: + port: 5432 diff --git a/helm/cardano-rosetta-java/charts/rosetta-api/Chart.yaml b/helm/cardano-rosetta-java/charts/rosetta-api/Chart.yaml new file mode 100644 index 0000000000..f7ec03d0cf --- /dev/null +++ b/helm/cardano-rosetta-java/charts/rosetta-api/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: rosetta-api +description: Cardano Rosetta Java API service +type: application +version: 2.0.0 +appVersion: "2.0.0" diff --git a/helm/cardano-rosetta-java/charts/rosetta-api/templates/_helpers.tpl b/helm/cardano-rosetta-java/charts/rosetta-api/templates/_helpers.tpl new file mode 100644 index 0000000000..8207b13c82 --- /dev/null +++ b/helm/cardano-rosetta-java/charts/rosetta-api/templates/_helpers.tpl @@ -0,0 +1,19 @@ +{{- define "rosetta-api.fullname" -}} +{{- printf "%s-rosetta-api" .Release.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "rosetta-api.commonLabels" -}} +app.kubernetes.io/name: rosetta-api +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: {{ .Values.global.releaseVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{- define "rosetta-api.selectorLabels" -}} +app: {{ include "rosetta-api.fullname" . }} +component: rosetta-api +{{- end }} + +{{- define "rosetta-api.profile" -}} +{{- .Values.global.profile | default "mid" }} +{{- end }} diff --git a/helm/cardano-rosetta-java/charts/rosetta-api/templates/deployment.yaml b/helm/cardano-rosetta-java/charts/rosetta-api/templates/deployment.yaml new file mode 100644 index 0000000000..f1b539bba4 --- /dev/null +++ b/helm/cardano-rosetta-java/charts/rosetta-api/templates/deployment.yaml @@ -0,0 +1,186 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "rosetta-api.fullname" . }} + labels: + {{- include "rosetta-api.commonLabels" . | nindent 4 }} + component: rosetta-api +spec: + replicas: {{ .Values.replicaCount | default 1 }} + selector: + matchLabels: + {{- include "rosetta-api.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "rosetta-api.selectorLabels" . | nindent 8 }} + {{- include "rosetta-api.commonLabels" . | nindent 8 }} + spec: + terminationGracePeriodSeconds: 30 + + initContainers: + - name: wait-for-postgres + image: postgres:16-alpine + command: + - sh + - -c + - | + echo "Waiting for PostgreSQL..." + until pg_isready \ + -h {{ include "cardano-rosetta-java.dbHost" . | quote }} \ + -p {{ .Values.global.db.port }} \ + -U {{ .Values.global.db.user | quote }}; do + echo "PostgreSQL not ready, retrying in 5s..." + sleep 5 + done + echo "PostgreSQL is ready" + resources: + {{- toYaml .Values.global.initContainerResources | nindent 12 }} + + - name: wait-for-indexer + image: curlimages/curl:8.5.0 + command: + - sh + - -c + - | + YACI_URL="http://{{ include "cardano-rosetta-java.yaciServiceName" . }}:9095" + echo "Waiting for yaci-indexer at ${YACI_URL}/actuator/health..." + until curl -sf "${YACI_URL}/actuator/health" > /dev/null 2>&1; do + echo "yaci-indexer not ready, retrying in 10s..." + sleep 10 + done + echo "yaci-indexer is healthy" + resources: + {{- toYaml .Values.global.initContainerResources | nindent 12 }} + + - name: copy-node-config + image: "cardanofoundation/cardano-rosetta-java-cardano-node:{{ .Values.global.cardanoNodeVersion }}" + command: ["sh", "-c", "cp -a /config/{{ .Values.global.network }}/. /shared-config/"] + volumeMounts: + - name: node-config + mountPath: /shared-config + resources: + {{- toYaml .Values.global.initContainerResources | nindent 12 }} + + containers: + - name: rosetta-api + image: "cardanofoundation/cardano-rosetta-java-api:{{ .Values.global.releaseVersion }}" + env: + - name: NETWORK + value: {{ .Values.global.network | quote }} + - name: API_SPRING_PROFILES_ACTIVE + value: "online" + - name: API_PORT + value: "8082" + - name: DB_HOST + value: {{ include "cardano-rosetta-java.dbHost" . | quote }} + - name: DB_PORT + value: {{ .Values.global.db.port | toString | quote }} + - name: DB_NAME + value: {{ .Values.global.db.name | quote }} + - name: DB_USER + value: {{ .Values.global.db.user | quote }} + - name: DB_SCHEMA + value: {{ .Values.global.db.schema | quote }} + - name: DB_SECRET + valueFrom: + secretKeyRef: + name: {{ include "cardano-rosetta-java.dbSecretName" . }} + key: db-secret + - name: CARDANO_NODE_VERSION + value: {{ .Values.global.cardanoNodeVersion | quote }} + - name: TOPOLOGY_FILEPATH + value: /config/topology.json + - name: GENESIS_SHELLEY_PATH + value: /config/shelley-genesis.json + - name: GENESIS_ALONZO_PATH + value: /config/alonzo-genesis.json + - name: GENESIS_CONWAY_PATH + value: /config/conway-genesis.json + - name: GENESIS_BYRON_PATH + value: /config/byron-genesis.json + - name: CARDANO_NODE_SUBMIT_HOST + value: {{ include "cardano-rosetta-java.nodeServiceName" . | quote }} + - name: NODE_SUBMIT_API_PORT + value: "8090" + - name: CARDANO_NODE_SOCKET_PATH + value: /tmp/node.socket + - name: YACI_HTTP_BASE_URL + value: {{ printf "http://%s:9095/api/v1" (include "cardano-rosetta-java.yaciServiceName" .) | quote }} + - name: HTTP_CONNECT_TIMEOUT_SECONDS + value: {{ .Values.env.httpConnectTimeoutSeconds | toString | quote }} + - name: HTTP_REQUEST_TIMEOUT_SECONDS + value: {{ .Values.env.httpRequestTimeoutSeconds | toString | quote }} + - name: SYNC_GRACE_SLOTS_COUNT + value: {{ .Values.env.syncGraceSlotsCount | toString | quote }} + - name: REMOVE_SPENT_UTXOS + value: {{ .Values.env.removeSpentUtxos | toString | quote }} + - name: REMOVE_SPENT_UTXOS_LAST_BLOCKS_GRACE_COUNT + value: {{ .Values.env.removeSpentUtxosLastBlocksGraceCount | toString | quote }} + - name: BLOCK_TRANSACTION_API_TIMEOUT_SECS + value: {{ .Values.env.blockTransactionApiTimeoutSecs | toString | quote }} + - name: DEVKIT_ENABLED + value: {{ .Values.env.devkitEnabled | toString | quote }} + - name: DEVKIT_URL + value: {{ .Values.env.devkitUrl | quote }} + - name: DEVKIT_PORT + value: {{ .Values.env.devkitPort | toString | quote }} + - name: TOKEN_REGISTRY_ENABLED + value: {{ .Values.env.tokenRegistryEnabled | toString | quote }} + - name: TOKEN_REGISTRY_BASE_URL + value: {{ .Values.env.tokenRegistryBaseUrl | quote }} + - name: TOKEN_REGISTRY_CACHE_TTL_HOURS + value: {{ .Values.env.tokenRegistryCacheTtlHours | toString | quote }} + - name: TOKEN_REGISTRY_LOGO_FETCH + value: {{ .Values.env.tokenRegistryLogoFetch | toString | quote }} + - name: TOKEN_REGISTRY_REQUEST_TIMEOUT_SECONDS + value: {{ .Values.env.tokenRegistryRequestTimeoutSeconds | toString | quote }} + {{- $profile := (include "rosetta-api.profile" .) }} + {{- $db := index .Values.global.profiles $profile "db" }} + - name: API_DB_POOL_MIN_COUNT + value: {{ $db.poolMin | toString | quote }} + - name: API_DB_POOL_MAX_COUNT + value: {{ $db.poolMax | toString | quote }} + - name: API_DB_POOL_MAX_LIFETIME_MS + value: "2000000" + - name: API_DB_POOL_CONNECTION_TIMEOUT_MS + value: "100000" + - name: API_DB_KEEP_ALIVE_MS + value: "60000" + - name: API_DB_LEAK_CONNECTIONS_WARNING_MS + value: "60000" + - name: API_DB_SHOW_SQL + value: "false" + - name: API_DB_MONITOR_PERFORMANCE + value: "false" + ports: + - name: http + containerPort: 8082 + protocol: TCP + volumeMounts: + - name: node-config + mountPath: /config + readOnly: true + readinessProbe: + httpGet: + path: /actuator/health + port: 8082 + initialDelaySeconds: 60 + periodSeconds: 15 + failureThreshold: 40 + timeoutSeconds: 10 + livenessProbe: + httpGet: + path: /actuator/health + port: 8082 + initialDelaySeconds: 120 + periodSeconds: 30 + failureThreshold: 5 + timeoutSeconds: 5 + resources: + {{- $res := index .Values.global.profiles $profile "resources" "api" }} + {{- toYaml $res | nindent 12 }} + + volumes: + - name: node-config + emptyDir: {} diff --git a/helm/cardano-rosetta-java/charts/rosetta-api/templates/ingress.yaml b/helm/cardano-rosetta-java/charts/rosetta-api/templates/ingress.yaml new file mode 100644 index 0000000000..d33a66743c --- /dev/null +++ b/helm/cardano-rosetta-java/charts/rosetta-api/templates/ingress.yaml @@ -0,0 +1,33 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "rosetta-api.fullname" . }} + labels: + {{- include "rosetta-api.commonLabels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- toYaml .Values.ingress.tls | nindent 4 }} + {{- end }} + rules: + - {{- if .Values.ingress.host }} + host: {{ .Values.ingress.host | quote }} + {{- end }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "rosetta-api.fullname" . }} + port: + number: {{ .Values.service.port }} +{{- end }} diff --git a/helm/cardano-rosetta-java/charts/rosetta-api/templates/service.yaml b/helm/cardano-rosetta-java/charts/rosetta-api/templates/service.yaml new file mode 100644 index 0000000000..7a9c706e41 --- /dev/null +++ b/helm/cardano-rosetta-java/charts/rosetta-api/templates/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "rosetta-api.fullname" . }} + labels: + {{- include "rosetta-api.commonLabels" . | nindent 4 }} + component: rosetta-api +spec: + type: {{ .Values.service.type | default "ClusterIP" }} + selector: + {{- include "rosetta-api.selectorLabels" . | nindent 4 }} + ports: + - name: http + port: {{ .Values.service.port }} + targetPort: 8082 + protocol: TCP diff --git a/helm/cardano-rosetta-java/charts/rosetta-api/values.yaml b/helm/cardano-rosetta-java/charts/rosetta-api/values.yaml new file mode 100644 index 0000000000..c95e1c5ce3 --- /dev/null +++ b/helm/cardano-rosetta-java/charts/rosetta-api/values.yaml @@ -0,0 +1,75 @@ +## ----------------------------------------------------------------------- +## rosetta-api subchart default values +## ----------------------------------------------------------------------- + +## Replica count for the API deployment +replicaCount: 1 + +## Service configuration +service: + port: 8082 + type: ClusterIP # ClusterIP | NodePort | LoadBalancer + +## Ingress configuration +ingress: + enabled: false + className: "" + annotations: {} + host: "" + tls: [] + +## Environment-specific tuning parameters +env: + httpConnectTimeoutSeconds: 5 # matches Docker Compose default + httpRequestTimeoutSeconds: 5 # matches Docker Compose default + syncGraceSlotsCount: 100 # matches Docker Compose default + removeSpentUtxos: true + removeSpentUtxosLastBlocksGraceCount: 129600 # 30 days @ ~20s/block; matches Docker Compose default + blockTransactionApiTimeoutSecs: 120 # intentionally higher than Docker (5s) for K8s network latency + devkitEnabled: false + devkitUrl: "" + devkitPort: 0 + tokenRegistryEnabled: false + tokenRegistryBaseUrl: "" # matches Docker Compose default (disabled by default) + tokenRegistryCacheTtlHours: 12 # matches Docker Compose default + tokenRegistryLogoFetch: false + tokenRegistryRequestTimeoutSeconds: 2 # matches Docker Compose default + +## Resource and DB pool profiles (inherited from parent, can be overridden) +profiles: + entry: + resources: + api: + requests: + cpu: "250m" + memory: "1Gi" + limits: + cpu: "1" + memory: "2Gi" + db: + poolMin: 12 + poolMax: 12 + mid: + resources: + api: + requests: + cpu: "500m" + memory: "2Gi" + limits: + cpu: "2" + memory: "4Gi" + db: + poolMin: 150 + poolMax: 150 + advanced: + resources: + api: + requests: + cpu: "1" + memory: "4Gi" + limits: + cpu: "4" + memory: "8Gi" + db: + poolMin: 100 + poolMax: 550 diff --git a/helm/cardano-rosetta-java/charts/yaci-indexer/Chart.yaml b/helm/cardano-rosetta-java/charts/yaci-indexer/Chart.yaml new file mode 100644 index 0000000000..84c515aea7 --- /dev/null +++ b/helm/cardano-rosetta-java/charts/yaci-indexer/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: yaci-indexer +description: Yaci Store blockchain indexer for Cardano Rosetta Java +type: application +version: 2.0.0 +appVersion: "2.0.0" diff --git a/helm/cardano-rosetta-java/charts/yaci-indexer/templates/_helpers.tpl b/helm/cardano-rosetta-java/charts/yaci-indexer/templates/_helpers.tpl new file mode 100644 index 0000000000..74df61d36b --- /dev/null +++ b/helm/cardano-rosetta-java/charts/yaci-indexer/templates/_helpers.tpl @@ -0,0 +1,19 @@ +{{- define "yaci-indexer.fullname" -}} +{{- printf "%s-yaci-indexer" .Release.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "yaci-indexer.commonLabels" -}} +app.kubernetes.io/name: yaci-indexer +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: {{ .Values.global.releaseVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{- define "yaci-indexer.selectorLabels" -}} +app: {{ include "yaci-indexer.fullname" . }} +component: yaci-indexer +{{- end }} + +{{- define "yaci-indexer.profile" -}} +{{- .Values.global.profile | default "mid" }} +{{- end }} diff --git a/helm/cardano-rosetta-java/charts/yaci-indexer/templates/deployment.yaml b/helm/cardano-rosetta-java/charts/yaci-indexer/templates/deployment.yaml new file mode 100644 index 0000000000..26ad592ef6 --- /dev/null +++ b/helm/cardano-rosetta-java/charts/yaci-indexer/templates/deployment.yaml @@ -0,0 +1,173 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "yaci-indexer.fullname" . }} + labels: + {{- include "yaci-indexer.commonLabels" . | nindent 4 }} + component: yaci-indexer +spec: + replicas: 1 + selector: + matchLabels: + {{- include "yaci-indexer.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "yaci-indexer.selectorLabels" . | nindent 8 }} + {{- include "yaci-indexer.commonLabels" . | nindent 8 }} + spec: + terminationGracePeriodSeconds: 30 + + initContainers: + - name: wait-for-postgres + image: postgres:16-alpine + command: + - sh + - -c + - | + echo "Waiting for PostgreSQL at {{ include "cardano-rosetta-java.dbHost" . }}:{{ .Values.global.db.port }}..." + until pg_isready \ + -h {{ include "cardano-rosetta-java.dbHost" . | quote }} \ + -p {{ .Values.global.db.port }} \ + -U {{ .Values.global.db.user | quote }}; do + echo "PostgreSQL not ready, retrying in 5s..." + sleep 5 + done + echo "PostgreSQL is ready" + resources: + {{- toYaml .Values.global.initContainerResources | nindent 12 }} + + - name: wait-for-node-tcp + image: busybox:1.36 + command: + - sh + - -c + - | + NODE_SERVICE="{{ include "cardano-rosetta-java.nodeServiceName" . }}" + NODE_SOCAT_PORT="3002" + echo "Waiting for cardano-node socat bridge at ${NODE_SERVICE}:${NODE_SOCAT_PORT}..." + until nc -z "${NODE_SERVICE}" "${NODE_SOCAT_PORT}" 2>/dev/null; do + echo "Node TCP not reachable yet, waiting 10s..." + sleep 10 + done + echo "cardano-node socat bridge is reachable, starting yaci-indexer" + resources: + {{- toYaml .Values.global.initContainerResources | nindent 12 }} + + - name: copy-node-config + image: "cardanofoundation/cardano-rosetta-java-cardano-node:{{ .Values.global.cardanoNodeVersion }}" + command: ["sh", "-c", "cp -a /config/{{ .Values.global.network }}/. /shared-config/"] + volumeMounts: + - name: node-config + mountPath: /shared-config + resources: + {{- toYaml .Values.global.initContainerResources | nindent 12 }} + + containers: + - name: yaci-indexer + image: "cardanofoundation/cardano-rosetta-java-indexer:{{ .Values.global.releaseVersion }}" + env: + - name: NETWORK + value: {{ .Values.global.network | quote }} + # Use n2c-socat profile for TCP socket bridge (K8s compatible) + - name: YACI_SPRING_PROFILES + value: "postgres,n2c-socat" + - name: HOST_N2C_SOCAT_HOST + value: {{ include "cardano-rosetta-java.nodeServiceName" . | quote }} + - name: HOST_N2C_SOCAT_PORT + value: "3002" + - name: CARDANO_NODE_HOST + value: {{ include "cardano-rosetta-java.nodeServiceName" . | quote }} + - name: CARDANO_NODE_PORT + value: "3001" + - name: DB_HOST + value: {{ include "cardano-rosetta-java.dbHost" . | quote }} + - name: DB_PORT + value: {{ .Values.global.db.port | toString | quote }} + - name: DB_NAME + value: {{ .Values.global.db.name | quote }} + - name: DB_USER + value: {{ .Values.global.db.user | quote }} + - name: DB_SCHEMA + value: {{ .Values.global.db.schema | quote }} + - name: DB_SECRET + valueFrom: + secretKeyRef: + name: {{ include "cardano-rosetta-java.dbSecretName" . }} + key: db-secret + - name: PROTOCOL_MAGIC + value: {{ .Values.global.protocolMagic | toString | quote }} + - name: GENESIS_SHELLEY_PATH + value: /config/shelley-genesis.json + - name: GENESIS_BYRON_PATH + value: /config/byron-genesis.json + - name: GENESIS_ALONZO_PATH + value: /config/alonzo-genesis.json + - name: GENESIS_CONWAY_PATH + value: /config/conway-genesis.json + - name: REMOVE_SPENT_UTXOS + value: {{ .Values.env.removeSpentUtxos | toString | quote }} + - name: REMOVE_SPENT_UTXOS_LAST_BLOCKS_GRACE_COUNT + value: {{ .Values.env.removeSpentUtxosLastBlocksGraceCount | toString | quote }} + - name: REMOVE_SPENT_UTXOS_BATCH_SIZE + value: {{ .Values.env.removeSpentUtxosBatchSize | toString | quote }} + - name: BLOCK_TRANSACTION_API_TIMEOUT_SECS + value: {{ .Values.env.blockTransactionApiTimeoutSecs | toString | quote }} + - name: SEARCH_LIMIT + value: {{ .Values.env.searchLimit | toString | quote }} + - name: CONTINUE_PARSING_ON_ERROR + value: {{ .Values.env.continueParsingOnError | toString | quote }} + - name: PEER_DISCOVERY + value: {{ .Values.env.peerDiscovery | toString | quote }} + - name: LOG + value: {{ .Values.env.logLevel | upper | quote }} + {{- $profile := include "yaci-indexer.profile" . }} + {{- $indexerDb := index .Values.global.profiles $profile "db" "indexerDb" }} + - name: INDEXER_DB_POOL_MIN_COUNT + value: {{ $indexerDb.poolMinCount | default .Values.env.indexerDbPoolMinCount | toString | quote }} + - name: INDEXER_DB_POOL_MAX_COUNT + value: {{ $indexerDb.poolMaxCount | default .Values.env.indexerDbPoolMaxCount | toString | quote }} + - name: INDEXER_DB_POOL_MAX_LIFETIME_MS + value: {{ .Values.env.indexerDbPoolMaxLifetimeMs | toString | quote }} + - name: INDEXER_DB_POOL_CONNECTION_TIMEOUT_MS + value: {{ .Values.env.indexerDbPoolConnectionTimeoutMs | toString | quote }} + - name: INDEXER_DB_KEEP_ALIVE_MS + value: {{ .Values.env.indexerDbKeepAliveMs | toString | quote }} + - name: INDEXER_DB_LEAK_CONNECTIONS_WARNING_MS + value: {{ .Values.env.indexerDbLeakConnectionsWarningMs | toString | quote }} + - name: INDEXER_DB_MONITOR_PERFORMANCE + value: {{ .Values.env.indexerDbMonitorPerformance | toString | quote }} + - name: INDEXER_DB_SHOW_SQL + value: {{ .Values.env.indexerDbShowSql | toString | quote }} + ports: + - name: http + containerPort: 9095 + protocol: TCP + volumeMounts: + - name: node-config + mountPath: /config + readOnly: true + readinessProbe: + httpGet: + path: /actuator/health + port: 9095 + initialDelaySeconds: 60 + periodSeconds: 15 + failureThreshold: 15 + timeoutSeconds: 5 + livenessProbe: + httpGet: + path: /actuator/health + port: 9095 + initialDelaySeconds: 120 + periodSeconds: 30 + failureThreshold: 5 + timeoutSeconds: 5 + resources: + {{- $profile := (include "yaci-indexer.profile" .) }} + {{- $res := index .Values.global.profiles $profile "resources" "indexer" }} + {{- toYaml $res | nindent 12 }} + + volumes: + - name: node-config + emptyDir: {} diff --git a/helm/cardano-rosetta-java/charts/yaci-indexer/templates/service.yaml b/helm/cardano-rosetta-java/charts/yaci-indexer/templates/service.yaml new file mode 100644 index 0000000000..81c674ecac --- /dev/null +++ b/helm/cardano-rosetta-java/charts/yaci-indexer/templates/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "yaci-indexer.fullname" . }} + labels: + {{- include "yaci-indexer.commonLabels" . | nindent 4 }} + component: yaci-indexer +spec: + type: ClusterIP + selector: + {{- include "yaci-indexer.selectorLabels" . | nindent 4 }} + ports: + - name: http + port: 9095 + targetPort: 9095 + protocol: TCP diff --git a/helm/cardano-rosetta-java/charts/yaci-indexer/values.yaml b/helm/cardano-rosetta-java/charts/yaci-indexer/values.yaml new file mode 100644 index 0000000000..653634fe35 --- /dev/null +++ b/helm/cardano-rosetta-java/charts/yaci-indexer/values.yaml @@ -0,0 +1,55 @@ +## ----------------------------------------------------------------------- +## yaci-indexer subchart default values +## ----------------------------------------------------------------------- + +## Environment-specific tuning parameters +env: + removeSpentUtxos: true + removeSpentUtxosLastBlocksGraceCount: 129600 # 30 days @ ~20s/block; matches Docker Compose default + removeSpentUtxosBatchSize: 3000 # matches Docker Compose default + blockTransactionApiTimeoutSecs: 120 # intentionally higher than Docker (5s) for K8s network latency + searchLimit: 100 # matches Docker Compose default + continueParsingOnError: true # matches Docker Compose default + peerDiscovery: false + logLevel: error # matches Docker Compose default (LOG=ERROR) + # HikariCP connection pool tuning — matches Docker Compose INDEXER_DB_* variables. + # Jar default pool size is 20, which is too small during heavy sync. + # All slots fill up → health endpoint can't get a connection → liveness probe fails → restart. + indexerDbPoolMinCount: 10 # INDEXER_DB_POOL_MIN_COUNT + indexerDbPoolMaxCount: 40 # INDEXER_DB_POOL_MAX_COUNT + indexerDbPoolMaxLifetimeMs: "2000000" # INDEXER_DB_POOL_MAX_LIFETIME_MS + indexerDbPoolConnectionTimeoutMs: 100000 # INDEXER_DB_POOL_CONNECTION_TIMEOUT_MS + indexerDbKeepAliveMs: 60000 # INDEXER_DB_KEEP_ALIVE_MS + indexerDbLeakConnectionsWarningMs: 60000 # INDEXER_DB_LEAK_CONNECTIONS_WARNING_MS + indexerDbMonitorPerformance: false # INDEXER_DB_MONITOR_PERFORMANCE + indexerDbShowSql: false # INDEXER_DB_SHOW_SQL + +## Resource profiles (inherited from parent, can be overridden) +profiles: + entry: + resources: + indexer: + requests: + cpu: "500m" + memory: "2Gi" + limits: + cpu: "2" + memory: "4Gi" + mid: + resources: + indexer: + requests: + cpu: "1" + memory: "4Gi" + limits: + cpu: "4" + memory: "8Gi" + advanced: + resources: + indexer: + requests: + cpu: "2" + memory: "8Gi" + limits: + cpu: "8" + memory: "16Gi" diff --git a/helm/cardano-rosetta-java/files/db-indexes.yaml b/helm/cardano-rosetta-java/files/db-indexes.yaml new file mode 120000 index 0000000000..13da05d397 --- /dev/null +++ b/helm/cardano-rosetta-java/files/db-indexes.yaml @@ -0,0 +1 @@ +../../../api/src/main/resources/config/db-indexes.yaml \ No newline at end of file diff --git a/helm/cardano-rosetta-java/templates/_helpers.tpl b/helm/cardano-rosetta-java/templates/_helpers.tpl new file mode 100644 index 0000000000..9155ea2318 --- /dev/null +++ b/helm/cardano-rosetta-java/templates/_helpers.tpl @@ -0,0 +1,122 @@ +{{/* +Expand the full name of the chart using the release name. +*/}} +{{- define "cardano-rosetta-java.fullname" -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Determine the active hardware profile. +*/}} +{{- define "cardano-rosetta-java.profile" -}} +{{- .Values.global.profile | default "mid" }} +{{- end }} + +{{/* +Common labels applied to every resource. +*/}} +{{- define "cardano-rosetta-java.commonLabels" -}} +app.kubernetes.io/name: cardano-rosetta-java +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: {{ .Values.global.releaseVersion | default .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels for pod matching. +*/}} +{{- define "cardano-rosetta-java.selectorLabels" -}} +app.kubernetes.io/name: cardano-rosetta-java +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Name of the Kubernetes Secret that holds the database password. +If global.db.existingSecret is set, use that; otherwise use the chart-managed secret. +*/}} +{{- define "cardano-rosetta-java.dbSecretName" -}} +{{- .Values.global.db.existingSecret | default (printf "%s-db-secret" .Release.Name | trunc 63 | trimSuffix "-") }} +{{- end }} + +{{/* +Resolve the database host. If an explicit host is provided in global.db.host +use it; otherwise fall back to the in-cluster PostgreSQL service name. +*/}} +{{- define "cardano-rosetta-java.dbHost" -}} +{{- if .Values.global.db.host -}} +{{- .Values.global.db.host -}} +{{- else -}} +{{- printf "%s-postgresql" .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end }} +{{- end }} + +{{/* +Index-applier job name. +*/}} +{{- define "cardano-rosetta-java.indexApplierName" -}} +{{- printf "%s-index-applier" .Release.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +DB indexes ConfigMap name. +*/}} +{{- define "cardano-rosetta-java.dbIndexesName" -}} +{{- printf "%s-db-indexes" .Release.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Load-tests ConfigMap name. +*/}} +{{- define "cardano-rosetta-java.loadTestsName" -}} +{{- printf "%s-load-tests" .Release.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Cardano-node service name (used cross-chart). +*/}} +{{- define "cardano-rosetta-java.nodeServiceName" -}} +{{- printf "%s-cardano-node" .Release.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Yaci-indexer service name (used cross-chart). +*/}} +{{- define "cardano-rosetta-java.yaciServiceName" -}} +{{- printf "%s-yaci-indexer" .Release.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Rosetta API service/deployment name. +*/}} +{{- define "cardano-rosetta-java.rosettaApiName" -}} +{{- printf "%s-rosetta-api" .Release.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Helm test pod name. +*/}} +{{- define "cardano-rosetta-java.testConnectionName" -}} +{{- printf "%s-test-connection" .Release.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Look up resource requests/limits for a given component under the active profile. +Usage: + {{- include "cardano-rosetta-java.profileResources" (dict "root" . "component" "api") | nindent 10 }} +*/}} +{{- define "cardano-rosetta-java.profileResources" -}} +{{- $profile := (include "cardano-rosetta-java.profile" .root) -}} +{{- $resources := index .root.Values.global.profiles $profile "resources" .component -}} +{{- toYaml $resources -}} +{{- end }} + +{{/* +Look up database tuning parameters for the active profile. +Usage: + {{- $dbSettings := include "cardano-rosetta-java.dbProfileSettings" . | fromYaml }} +*/}} +{{- define "cardano-rosetta-java.dbProfileSettings" -}} +{{- $profile := (include "cardano-rosetta-java.profile" .) -}} +{{- $db := index .Values.global.profiles $profile "db" -}} +{{- toYaml $db -}} +{{- end }} diff --git a/helm/cardano-rosetta-java/templates/configmap-db-indexes.yaml b/helm/cardano-rosetta-java/templates/configmap-db-indexes.yaml new file mode 100644 index 0000000000..2b00ffac9b --- /dev/null +++ b/helm/cardano-rosetta-java/templates/configmap-db-indexes.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "cardano-rosetta-java.dbIndexesName" . }} + labels: + {{- include "cardano-rosetta-java.commonLabels" . | nindent 4 }} +data: + db-indexes.yaml: | + {{- .Files.Get "files/db-indexes.yaml" | nindent 4 }} diff --git a/helm/cardano-rosetta-java/templates/helm-test-pod.yaml b/helm/cardano-rosetta-java/templates/helm-test-pod.yaml new file mode 100644 index 0000000000..23748b8758 --- /dev/null +++ b/helm/cardano-rosetta-java/templates/helm-test-pod.yaml @@ -0,0 +1,58 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{ include "cardano-rosetta-java.testConnectionName" . }} + labels: + {{- include "cardano-rosetta-java.commonLabels" . | nindent 4 }} + component: test + annotations: + helm.sh/hook: test + helm.sh/hook-delete-policy: before-hook-creation +spec: + restartPolicy: Never + containers: + - name: test-api-endpoints + image: curlimages/curl:8.5.0 + command: + - sh + - -c + - | + set -e + NETWORK="{{ .Values.global.network }}" + API_URL="http://{{ include "cardano-rosetta-java.yaciServiceName" . | replace "yaci-indexer" "rosetta-api" }}:8082" + YACI_URL="http://{{ include "cardano-rosetta-java.yaciServiceName" . }}:9095" + + echo "=== Testing Rosetta API ===" + + echo "1. Testing /network/list..." + curl -sf "${API_URL}/network/list" \ + -H "Content-Type: application/json" \ + -d '{"metadata":{}}' -X POST + echo "" + echo "PASS: /network/list" + + echo "2. Testing /network/options..." + curl -sf "${API_URL}/network/options" \ + -H "Content-Type: application/json" \ + -d "{\"network_identifier\":{\"blockchain\":\"cardano\",\"network\":\"${NETWORK}\"},\"metadata\":{}}" \ + -X POST + echo "" + echo "PASS: /network/options" + + echo "3. Testing /network/status..." + curl -sf "${API_URL}/network/status" \ + -H "Content-Type: application/json" \ + -d "{\"network_identifier\":{\"blockchain\":\"cardano\",\"network\":\"${NETWORK}\"},\"metadata\":{}}" \ + -X POST + echo "" + echo "PASS: /network/status" + + echo "=== Testing Yaci Indexer ===" + echo "4. Testing yaci-indexer /actuator/health..." + curl -sf "${YACI_URL}/actuator/health" + echo "" + echo "PASS: yaci-indexer /actuator/health" + + echo "=== All tests passed ===" + resources: + {{- toYaml .Values.global.initContainerResources | nindent 8 }} diff --git a/helm/cardano-rosetta-java/templates/index-applier-job.yaml b/helm/cardano-rosetta-java/templates/index-applier-job.yaml new file mode 100644 index 0000000000..e861d07b6d --- /dev/null +++ b/helm/cardano-rosetta-java/templates/index-applier-job.yaml @@ -0,0 +1,86 @@ +{{- if .Values.indexApplier.enabled }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "cardano-rosetta-java.indexApplierName" . }} + labels: + {{- include "cardano-rosetta-java.commonLabels" . | nindent 4 }} + component: index-applier + {{- if eq (default "automatic" .Values.indexApplier.mode) "hook" }} + annotations: + helm.sh/hook: post-install,post-upgrade + helm.sh/hook-weight: "10" + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded + {{- end }} +spec: + backoffLimit: 5 + activeDeadlineSeconds: 259200 + {{- if ne (default "automatic" .Values.indexApplier.mode) "hook" }} + # Auto-cleanup after 24h so future helm upgrades can recreate the Job without conflicts. + ttlSecondsAfterFinished: 86400 + {{- end }} + template: + metadata: + labels: + component: index-applier + spec: + restartPolicy: OnFailure + initContainers: + - name: wait-for-api + image: curlimages/curl:8.5.0 + command: + - sh + - -c + - | + API_URL="http://{{ include "cardano-rosetta-java.rosettaApiName" . }}:8082" + NETWORK="{{ .Values.global.network }}" + echo "Waiting for Rosetta API at ${API_URL}..." + until curl -sf "${API_URL}/network/options" \ + -H "Content-Type: application/json" \ + -d "{\"network_identifier\":{\"blockchain\":\"cardano\",\"network\":\"${NETWORK}\"},\"metadata\":{}}" \ + -X POST > /dev/null 2>&1; do + echo "API not ready, retrying in 30s..." + sleep 30 + done + echo "API is healthy" + resources: + {{- toYaml .Values.global.initContainerResources | nindent 12 }} + containers: + - name: index-applier + image: "cardanofoundation/cardano-rosetta-java-postgres:{{ .Values.global.pgVersionTag }}" + command: ["/sbin/apply-indexes.sh"] + env: + - name: DB_HOST + value: {{ include "cardano-rosetta-java.dbHost" . | quote }} + - name: DB_PORT + value: {{ .Values.global.db.port | toString | quote }} + - name: DB_NAME + value: {{ .Values.global.db.name | quote }} + - name: DB_USER + value: {{ .Values.global.db.user | quote }} + - name: DB_SCHEMA + value: {{ .Values.global.db.schema | quote }} + - name: DB_SECRET + valueFrom: + secretKeyRef: + name: {{ include "cardano-rosetta-java.dbSecretName" . }} + key: db-secret + - name: NETWORK + value: {{ .Values.global.network | quote }} + - name: API_URL + value: "http://{{ include "cardano-rosetta-java.rosettaApiName" . }}:8082" + - name: POLL_INTERVAL + value: {{ .Values.indexApplier.pollInterval | toString | quote }} + volumeMounts: + - name: db-indexes + mountPath: /config/db-indexes.yaml + subPath: db-indexes.yaml + resources: + {{- $profile := (include "cardano-rosetta-java.profile" .) }} + {{- $res := index .Values.global.profiles $profile "resources" "indexApplier" }} + {{- toYaml $res | nindent 12 }} + volumes: + - name: db-indexes + configMap: + name: {{ include "cardano-rosetta-java.dbIndexesName" . }} +{{- end }} diff --git a/helm/cardano-rosetta-java/values-k3s.yaml b/helm/cardano-rosetta-java/values-k3s.yaml new file mode 100644 index 0000000000..494e4976e0 --- /dev/null +++ b/helm/cardano-rosetta-java/values-k3s.yaml @@ -0,0 +1,44 @@ +## ----------------------------------------------------------------------- +## K3s local development overrides +## ----------------------------------------------------------------------- +global: + profile: entry + storage: + cardanoNode: + size: 100Gi + storageClass: "local-path" + postgresql: + size: 50Gi + storageClass: "local-path" + ## Override entry profile resources for k3s (very conservative) + profiles: + entry: + resources: + cardanoNode: + requests: + cpu: "1" + memory: "4Gi" + limits: + cpu: "2" + memory: "8Gi" + postgresql: + requests: + cpu: "1" + memory: "2Gi" + limits: + cpu: "2" + memory: "6Gi" + indexer: + requests: + cpu: "500m" + memory: "1Gi" + limits: + cpu: "1" + memory: "2Gi" + api: + requests: + cpu: "250m" + memory: "512Mi" + limits: + cpu: "1" + memory: "1Gi" diff --git a/helm/cardano-rosetta-java/values-preprod.yaml b/helm/cardano-rosetta-java/values-preprod.yaml new file mode 100644 index 0000000000..0dafcb6a8c --- /dev/null +++ b/helm/cardano-rosetta-java/values-preprod.yaml @@ -0,0 +1,28 @@ +## ----------------------------------------------------------------------- +## Preprod network overrides +## ----------------------------------------------------------------------- +global: + network: preprod + protocolMagic: "1" + profile: entry + storage: + cardanoNode: + size: 100Gi + postgresql: + size: 50Gi + +yaci-indexer: + env: + removeSpentUtxos: false + peerDiscovery: true + continueParsingOnError: false + logLevel: INFO + +rosetta-api: + env: + removeSpentUtxos: false + syncGraceSlotsCount: 100 + tokenRegistryEnabled: true # DC preprod: TOKEN_REGISTRY_ENABLED=true + tokenRegistryBaseUrl: "https://preprod.tokens.cardano.org/api" # DC preprod: TOKEN_REGISTRY_BASE_URL + tokenRegistryLogoFetch: true # DC preprod: TOKEN_REGISTRY_LOGO_FETCH=true + tokenRegistryCacheTtlHours: 1 # DC preprod: TOKEN_REGISTRY_CACHE_TTL_HOURS=1 diff --git a/helm/cardano-rosetta-java/values.yaml b/helm/cardano-rosetta-java/values.yaml new file mode 100644 index 0000000000..b2977e62d0 --- /dev/null +++ b/helm/cardano-rosetta-java/values.yaml @@ -0,0 +1,334 @@ +## ----------------------------------------------------------------------- +## Global configuration shared across all subcharts +## ----------------------------------------------------------------------- +global: + network: mainnet + protocolMagic: "764824073" + releaseVersion: "2.1.0" + cardanoNodeVersion: "10.5.4-config" + pgVersionTag: "REL_18_0" + mithrilVersion: "2543.1-hotfix" + profile: mid + + db: + name: "rosetta-java" + user: "rosetta_db_admin" + # password is required — pass via --set global.db.password= or a sealed secret + password: "" + # existingSecret: if set, skip creating the Secret and use this existing secret name instead. + # The secret must contain a key named "db-secret" with the database password. + # Useful with Sealed Secrets, External Secrets Operator, or Vault. + existingSecret: "" + schema: "public" + port: 5432 + # host: leave empty to use the in-cluster postgresql service automatically + host: "" + + # Set to false for offline mode (API only, no node sync wait) + sync: true + + # Resource requests/limits for all init containers (wait-for-* sidecars). + # Tune down if nodes are resource-constrained. + initContainerResources: + requests: + cpu: "50m" + memory: "32Mi" + limits: + cpu: "200m" + memory: "128Mi" + + + ## ------------------------------------------------------------------- + ## Hardware profiles: entry / mid / advanced + ## (nested under global so subcharts can access via .Values.global.profiles) + ## ------------------------------------------------------------------- + profiles: + entry: + resources: + cardanoNode: + requests: + cpu: "1" + memory: "6Gi" + limits: + cpu: "4" + memory: "12Gi" + postgresql: + requests: + cpu: "1" + memory: "6Gi" + limits: + cpu: "4" + memory: "12Gi" + indexer: + requests: + cpu: "500m" + memory: "2Gi" + limits: + cpu: "2" + memory: "4Gi" + api: + requests: + cpu: "250m" + memory: "1Gi" + limits: + cpu: "1" + memory: "2Gi" + mithril: + requests: + cpu: "250m" + memory: "256Mi" + limits: + cpu: "1" + memory: "1Gi" + submitApi: + requests: + cpu: "100m" + memory: "256Mi" + limits: + cpu: "500m" + memory: "1Gi" + indexApplier: + requests: + cpu: "100m" + memory: "256Mi" + limits: + cpu: "500m" + memory: "512Mi" + db: + poolMin: 12 + poolMax: 12 + indexerDb: + poolMinCount: 12 + poolMaxCount: 12 + maxConnections: 120 + sharedBuffers: "1GB" + effectiveCacheSize: "2GB" + workMem: "16MB" + maintenanceWorkMem: "128MB" + walBuffers: "16MB" + checkpointCompletionTarget: "0.7" + randomPageCost: "3.0" + effectiveIoConcurrency: 1 + parallelTupleCost: "0.1" + parallelSetupCost: "1000" + maxParallelWorkersPerGather: 2 + maxParallelWorkers: 4 + seqPageCost: "1.0" + jit: "off" + bgwriterLruMaxpages: 50 + bgwriterDelay: "500ms" + + mid: + resources: + cardanoNode: + requests: + cpu: "2" + memory: "12Gi" + limits: + cpu: "8" + memory: "24Gi" + postgresql: + requests: + cpu: "2" + memory: "12Gi" + limits: + cpu: "8" + memory: "24Gi" + indexer: + requests: + cpu: "1" + memory: "4Gi" + limits: + cpu: "4" + memory: "8Gi" + api: + requests: + cpu: "500m" + memory: "2Gi" + limits: + cpu: "2" + memory: "4Gi" + mithril: + requests: + cpu: "500m" + memory: "512Mi" + limits: + cpu: "2" + memory: "2Gi" + submitApi: + requests: + cpu: "100m" + memory: "256Mi" + limits: + cpu: "500m" + memory: "1Gi" + indexApplier: + requests: + cpu: "100m" + memory: "256Mi" + limits: + cpu: "500m" + memory: "512Mi" + db: + poolMin: 150 + poolMax: 150 + indexerDb: + poolMinCount: 50 + poolMaxCount: 50 + maxConnections: 300 + sharedBuffers: "4GB" + effectiveCacheSize: "8GB" + workMem: "64MB" + maintenanceWorkMem: "512MB" + walBuffers: "512MB" + checkpointCompletionTarget: "0.7" + randomPageCost: "1.3" + effectiveIoConcurrency: 2 + parallelTupleCost: "0.05" + parallelSetupCost: "500" + maxParallelWorkersPerGather: 4 + maxParallelWorkers: 8 + seqPageCost: "1.0" + jit: "off" + bgwriterLruMaxpages: 200 + bgwriterDelay: "200ms" + autovacuumMaxWorkers: 5 + + advanced: + resources: + cardanoNode: + requests: + cpu: "4" + memory: "24Gi" + limits: + cpu: "16" + memory: "48Gi" + postgresql: + requests: + cpu: "4" + memory: "24Gi" + limits: + cpu: "16" + memory: "48Gi" + indexer: + requests: + cpu: "2" + memory: "8Gi" + limits: + cpu: "8" + memory: "16Gi" + api: + requests: + cpu: "1" + memory: "4Gi" + limits: + cpu: "4" + memory: "8Gi" + mithril: + requests: + cpu: "1" + memory: "1Gi" + limits: + cpu: "4" + memory: "4Gi" + submitApi: + requests: + cpu: "250m" + memory: "512Mi" + limits: + cpu: "1" + memory: "2Gi" + indexApplier: + requests: + cpu: "250m" + memory: "512Mi" + limits: + cpu: "1" + memory: "1Gi" + db: + poolMin: 100 + poolMax: 550 + indexerDb: + poolMinCount: 50 + poolMaxCount: 150 + maxConnections: 600 + sharedBuffers: "32GB" + effectiveCacheSize: "32GB" + workMem: "96MB" + maintenanceWorkMem: "2GB" + walBuffers: "512MB" + checkpointCompletionTarget: "0.9" + randomPageCost: "1.2" + effectiveIoConcurrency: 200 + parallelTupleCost: "0.03" + parallelSetupCost: "300" + maxParallelWorkersPerGather: 8 + maxParallelWorkers: 16 + seqPageCost: "0.5" + jit: "off" + bgwriterLruMaxpages: 300 + bgwriterDelay: "200ms" + autovacuumMaxWorkers: 5 + + ## ------------------------------------------------------------------- + ## Storage configuration + ## (nested under global so subcharts can access via .Values.global.storage) + ## ------------------------------------------------------------------- + storage: + cardanoNode: + size: 500Gi + storageClass: "" + postgresql: + size: 200Gi + storageClass: "" + + ## ------------------------------------------------------------------- + ## Mithril snapshot download + ## (nested under global so subcharts can access via .Values.global.mithril) + ## ------------------------------------------------------------------- + mithril: + enabled: true + sync: true + snapshotDigest: "latest" + # aggregatorEndpoint, genesisVerificationKey, ancillaryVerificationKey are intentionally + # left empty. The entrypoint resolves all three automatically from NETWORK + # (mainnet / preprod / preview) — identical to Docker Compose behaviour. + # Override only for air-gapped environments or custom aggregators. + aggregatorEndpoint: "" + genesisVerificationKey: "" + ancillaryVerificationKey: "" + +## ----------------------------------------------------------------------- +## Index applier job +## ----------------------------------------------------------------------- +indexApplier: + enabled: true + # "automatic" (default): plain Job — runs with the release without needing helm hooks. + # Compatible with GitOps/ArgoCD (no --no-hooks needed). + # The job is auto-cleaned after 24h via ttlSecondsAfterFinished. + # "hook": legacy helm post-install/post-upgrade hook (use for operator-controlled rollouts). + mode: automatic + pollInterval: 60 + +## ----------------------------------------------------------------------- +## Stress testing with k6 +## ----------------------------------------------------------------------- +stressTesting: + enabled: false + profile: smoke + targetUrl: "http://rosetta-rosetta-api:8082" + +## ----------------------------------------------------------------------- +## Subchart toggles +## ----------------------------------------------------------------------- +cardano-node: + enabled: true + +postgresql: + enabled: true +yaci-indexer: + enabled: true + +rosetta-api: + enabled: true + diff --git a/tests/data-endpoints/client.py b/tests/data-endpoints/client.py index 5265214fdc..841fe908f1 100644 --- a/tests/data-endpoints/client.py +++ b/tests/data-endpoints/client.py @@ -246,6 +246,18 @@ def network_options(self, network_identifier: dict | None = None) -> httpx.Respo body = self._build_body(network_identifier=network_identifier) return self._post("/network/options", body, schema_name="NetworkOptionsResponse") + def call( + self, + method: str, + parameters: dict | None = None, + network_identifier: dict | None = None, + ) -> httpx.Response: + """Call a supported method on the /call endpoint.""" + body = self._build_body(network_identifier=network_identifier) + body["method"] = method + body["parameters"] = parameters or {} + return self._post("/call", body, schema_name="CallResponse") + def block( self, network_identifier: dict | None = None, diff --git a/tests/data-endpoints/test_network_endpoints.py b/tests/data-endpoints/test_network_endpoints.py index 078384d4fa..5458443ead 100644 --- a/tests/data-endpoints/test_network_endpoints.py +++ b/tests/data-endpoints/test_network_endpoints.py @@ -144,4 +144,24 @@ def test_returns_network_capabilities(self, client, network): assert isinstance(allow.get("historical_balance_lookup"), bool) + call_methods = allow.get("call_methods", []) + assert isinstance(call_methods, list), "call_methods must be a list" + assert call_methods, "call_methods must not be empty when /call is supported" + + @pytest.mark.pr + def test_call_methods_are_recognized(self, client, network): + """Every method listed in call_methods must be recognized by /call.""" + options = client.network_options().json() + call_methods = options.get("allow", {}).get("call_methods", []) + + assert call_methods, "call_methods must not be empty when /call is supported" + + for method in call_methods: + response = client.call(method=method) + if response.status_code != 200: + error = response.json() + assert error.get("code") != 5050, ( + f"Method '{method}' advertised in call_methods but /call returns 'not supported'" + ) + # Error handling tests moved to test_error_handling.py diff --git a/tests/integration/golden_examples/rosetta_java/construction/derive/invalid_address_type.json b/tests/integration/golden_examples/rosetta_java/construction/derive/invalid_address_type.json new file mode 100644 index 0000000000..0361441511 --- /dev/null +++ b/tests/integration/golden_examples/rosetta_java/construction/derive/invalid_address_type.json @@ -0,0 +1,22 @@ +{ + "test_name": "(-) invalid address type", + "description": "Negative test: unrecognized address_type returns error instead of silently defaulting to Enterprise", + "request_body": { + "network_identifier": { + "blockchain": "cardano", + "network": "{{networkId}}" + }, + "public_key": { + "hex_bytes": "159abeeecdf167ccc0ea60b30f9522154a0d74161aeb159fb43b6b0695f057b3", + "curve_type": "edwards25519" + }, + "metadata": { + "address_type": "InvalidType" + } + }, + "expected_error": { + "code": 4016, + "message": "Provided address type is invalid", + "retriable": false + } +} diff --git a/tests/integration/golden_examples/rosetta_java/data/network/network_options_operation_types.json b/tests/integration/golden_examples/rosetta_java/data/network/network_options_operation_types.json index 916b3c6650..34ac3e5863 100644 --- a/tests/integration/golden_examples/rosetta_java/data/network/network_options_operation_types.json +++ b/tests/integration/golden_examples/rosetta_java/data/network/network_options_operation_types.json @@ -508,7 +508,10 @@ } ], "historical_balance_lookup": true, - "call_methods": [], + "call_methods": [ + "get_parse_error_blocks", + "mark_parse_error_block_checked" + ], "balance_exemptions": [], "mempool_coins": false } diff --git a/yaci-indexer/src/main/resources/application.properties b/yaci-indexer/src/main/resources/application.properties index f576cb8599..1e97c283d2 100644 --- a/yaci-indexer/src/main/resources/application.properties +++ b/yaci-indexer/src/main/resources/application.properties @@ -122,8 +122,15 @@ store.utxo.pruning-safe-blocks=${REMOVE_SPENT_UTXOS_LAST_BLOCKS_GRACE_COUNT:1296 store.utxo.pruning-interval=10800 store.utxo.pruning-batch-size=${REMOVE_SPENT_UTXOS_BATCH_SIZE:3000} logging.level.com.bloxbean.cardano.yaci.store=${LOG:error} -spring.datasource.hikari.maximum-pool-size=20 -spring.datasource.hikari.minimum-idle=5 +spring.datasource.hikari.pool-name=CardanoRosettaJavaIndexerDBPool +spring.datasource.hikari.minimum-idle=${INDEXER_DB_POOL_MIN_COUNT:5} +spring.datasource.hikari.maximum-pool-size=${INDEXER_DB_POOL_MAX_COUNT:20} +spring.datasource.hikari.max-lifetime=${INDEXER_DB_POOL_MAX_LIFETIME_MS:2000000} +spring.datasource.hikari.connection-timeout=${INDEXER_DB_POOL_CONNECTION_TIMEOUT_MS:100000} +spring.datasource.hikari.keepalive-time=${INDEXER_DB_KEEP_ALIVE_MS:60000} +spring.datasource.hikari.leak-detection-threshold=${INDEXER_DB_LEAK_CONNECTIONS_WARNING_MS:60000} +spring.datasource.hikari.register-mbeans=${INDEXER_DB_MONITOR_PERFORMANCE:false} +spring.jpa.properties.hibernate.format_sql=${INDEXER_DB_SHOW_SQL:false} store.epoch.endpoints.epoch.local.enabled=true