diff --git a/docs/deployment/stress-testing.mdx b/docs/deployment/stress-testing.mdx index 0959c925b..9f9dc9716 100644 --- a/docs/deployment/stress-testing.mdx +++ b/docs/deployment/stress-testing.mdx @@ -35,18 +35,18 @@ The primary parameters that affect the specification requirements for Keep are: ### Testing Scenarios: -- **Low Volume (< 10,000 total alerts, 100's of alerts per day)**: +- **Low Volume (< 10,000 total alerts, hundreds of alerts per day)**: - **Setup**: Use a standard relational database (e.g., MySQL, PostgreSQL) with default configurations. - **Expectations**: Keep should handle queries and alert ingestion with minimal resource usage. -- **Medium Volume (10,000 - 100,000 total alerts, 1000's of alerts per day)**: +- **Medium Volume (10,000 - 100,000 total alerts, thousands of alerts per day)**: - **Setup**: Scale the database to larger instances or clusters. Adjust best practices to the DB (e.g. increasing innodb_buffer_pool_size) - **Expectations**: CPU and RAM usage should increase proportionally but remain within acceptable limits. -3. **High Volume (100,000 - 1,000,000 total alerts, 5000's of alerts per day)**: +3. **High Volume (100,000 - 1,000,000 total alerts, >five thousands of alerts per day)**: - **Setup**: Deploy Keep with Elasticsearch for storing alerts as documents. - **Expectations**: The system should maintain performance levels despite the large alert volume, with increased resource usage managed through scaling strategies. -4. **Very High Volume (> 1,000,000 total alerts, 10k's of alerts per day)**: +4. **Very High Volume (> 1,000,000 total alerts, tens of thousands of alerts per day)**: - **Setup**: Deploy Keep with Elasticsearch for storing alerts as documents. - **Setup #2**: Deploy Keep with Redis and with ARQ to use Redis as a queue. diff --git a/keep/topologies/topologies_service.py b/keep/topologies/topologies_service.py index c0cf55170..1529a7f6f 100644 --- a/keep/topologies/topologies_service.py +++ b/keep/topologies/topologies_service.py @@ -3,6 +3,7 @@ from pydantic import ValidationError from sqlalchemy.orm import joinedload, selectinload from uuid import UUID +import json from sqlmodel import Session, select @@ -49,22 +50,38 @@ def get_service_application_ids_dict( TopologyServiceApplication.service_id, get_aggreated_field( session, - TopologyServiceApplication.application_id, # type: ignore + TopologyServiceApplication.application_id, # type: ignore "application_ids", ), - ) # type: ignore + ) .where(TopologyServiceApplication.service_id.in_(service_ids)) .group_by(TopologyServiceApplication.service_id) ) results = session.exec(query).all() + dialect_name = session.bind.dialect.name if session.bind else "" + result = {} if session.bind is None: raise ValueError("Session is not bound to a database") - if session.bind.dialect.name == "sqlite": - result = {} - for service_id, application_ids in results: - result[service_id] = [UUID(app_id) for app_id in application_ids.split(",")] - return result - return {service_id: application_ids for service_id, application_ids in results} + for application_id, service_ids in results: + if dialect_name == "postgresql": + # PostgreSQL returns a list of UUIDs + pass + elif dialect_name == "mysql": + # MySQL returns a JSON string, so we need to parse it + service_ids = json.loads(service_ids) + elif dialect_name == "sqlite": + # SQLite returns a comma-separated string + service_ids = [UUID(id) for id in service_ids.split(",")] + else: + if service_ids and isinstance(service_ids[0], UUID): + # If it's already a list of UUIDs (like in PostgreSQL), use it as is + pass + else: + # For any other case, try to convert to UUID + service_ids = [UUID(str(id)) for id in service_ids] + result[application_id] = service_ids + + return result class TopologiesService: diff --git a/tests/test_topology.py b/tests/test_topology.py index 225d97bb4..12e56f53f 100644 --- a/tests/test_topology.py +++ b/tests/test_topology.py @@ -27,7 +27,7 @@ def create_service(db_session, tenant_id, id): service = TopologyService( tenant_id=tenant_id, service="test_service_" + id, - display_name="Test Service", + display_name=id, repository="test_repository", tags=["test_tag"], description="test_description", @@ -72,21 +72,28 @@ def test_get_all_topology_data(db_session): def test_get_applications_by_tenant_id(db_session): service_1 = create_service(db_session, SINGLE_TENANT_UUID, "1") service_2 = create_service(db_session, SINGLE_TENANT_UUID, "2") - application = TopologyApplication( + application_1 = TopologyApplication( tenant_id=SINGLE_TENANT_UUID, name="Test Application", services=[service_1, service_2], ) - db_session.add(application) + application_2 = TopologyApplication( + tenant_id=SINGLE_TENANT_UUID, + name="Test Application 2", + services=[service_1], + ) + db_session.add(application_1) + db_session.add(application_2) db_session.commit() result = TopologiesService.get_applications_by_tenant_id( SINGLE_TENANT_UUID, db_session ) - assert len(result) == 1 + assert len(result) == 2 assert result[0].name == "Test Application" assert len(result[0].services) == 2 - + assert result[1].name == "Test Application 2" + assert len(result[1].services) == 1 def test_create_application_by_tenant_id(db_session): application_dto = TopologyApplicationDtoIn(name="New Application", services=[]) @@ -171,21 +178,29 @@ def test_get_applications(db_session, client, test_app): service_1 = create_service(db_session, SINGLE_TENANT_UUID, "1") service_2 = create_service(db_session, SINGLE_TENANT_UUID, "2") + service_3 = create_service(db_session, SINGLE_TENANT_UUID, "3") - application = TopologyApplication( + application_1 = TopologyApplication( tenant_id=SINGLE_TENANT_UUID, name="Test Application", services=[service_1, service_2], ) - db_session.add(application) + application_2 = TopologyApplication( + tenant_id=SINGLE_TENANT_UUID, + name="Test Application 2", + services=[service_3], + ) + db_session.add(application_1) + db_session.add(application_2) db_session.commit() response = client.get( "/topology/applications", headers={"x-api-key": VALID_API_KEY} ) assert response.status_code == 200 - assert len(response.json()) == 1 + assert len(response.json()) == 2 assert response.json()[0]["name"] == "Test Application" + assert response.json()[1]["services"][0]["name"] == "3" @pytest.mark.parametrize("test_app", ["NO_AUTH"], indirect=True)