SIENTIAPDE-1646
Update project configuration and dependencies - Added .mypy_cache and .cursor to .gitignore. - Changed asyncio_default_fixture_loop_scope and asyncio_default_test_loop_scope to "session" in pyproject.toml. - Updated e2e testing dependencies in requirements-dev.txt, replacing fakeredis and mongomock with pytest-httpserver. - Updated requirements.txt to use sientia_do instead of a specific git commit. - Modified sonar-project.properties to remove a file from coverage exclusions. - Enhanced E2E test fixtures in e2e/conftest.py for better container management. - Cleaned up e2e test files related to CoreScouter and PIWebAPIScouter workflows.
This commit is contained in:
@@ -7,7 +7,7 @@ with workflow.unsafe.imports_passed_through():
|
||||
|
||||
from sientia_do.notifications.handlers import CoreNotificationHandler as NotificationHandler
|
||||
from sientia_do.observability.logger import Logger
|
||||
from sientia_do.temporal.activities.postgres import Postgres
|
||||
from sientia_do.temporal.activities.postgres_sync import Postgres
|
||||
|
||||
from scouter.activities.api import API
|
||||
from scouter.activities.gates import Gates
|
||||
|
||||
@@ -9,7 +9,7 @@ with workflow.unsafe.imports_passed_through():
|
||||
from sientia_do.observability.logger import Logger
|
||||
from sientia_do.observability.metrics_controller import MetricsController
|
||||
from sientia_do.observability.sientia_monitoring import SientiaMonitoring
|
||||
from sientia_do.repository.pi_web_api_client import PIWebAPIClient
|
||||
from sientia_do.repository.pi_web_api_client_sync import PIWebAPIClient
|
||||
from sientia_do.temporal.constants import DATETIME_FORMAT_WITH_TZ
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ class API(SientiaMonitoring):
|
||||
SientiaMonitoring.shutdown(self)
|
||||
|
||||
@activity.defn(name='get_tag_values')
|
||||
async def get_tag_values(self, input_data: dict[str, Any]) -> list[dict]:
|
||||
def get_tag_values(self, input_data: dict[str, Any]) -> list[dict]:
|
||||
"""
|
||||
Retrieve tag values from PI Web API for specified WebIds.
|
||||
|
||||
@@ -123,7 +123,7 @@ class API(SientiaMonitoring):
|
||||
self.info(f'Getting tag values from {endpoint}', metadata=metadata)
|
||||
self.debug(f'Web IDs: {web_ids}', metadata=metadata)
|
||||
try:
|
||||
latest_values = await self.pi_web_api_client.get_latest_values_df(
|
||||
latest_values = self.pi_web_api_client.get_latest_values_df(
|
||||
endpoint=endpoint,
|
||||
web_ids=web_ids,
|
||||
start_time=period,
|
||||
@@ -134,7 +134,7 @@ class API(SientiaMonitoring):
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
await self.send_notification_async(
|
||||
self.send_notification(
|
||||
metadata=metadata,
|
||||
notification_id='PI_WEB_API_REQUEST_ERROR',
|
||||
message=f'Error getting tag values from PI Web API: {e}',
|
||||
|
||||
@@ -59,7 +59,7 @@ class Gates(SientiaMonitoring):
|
||||
"""
|
||||
SientiaMonitoring.shutdown(self)
|
||||
|
||||
async def apply_aggregation(
|
||||
def apply_aggregation(
|
||||
self, values: DataFrame, aggr_function: str, metadata: dict[str, Any]
|
||||
) -> float | None | str:
|
||||
"""
|
||||
@@ -82,31 +82,16 @@ class Gates(SientiaMonitoring):
|
||||
Raises:
|
||||
NotificationError: If invalid aggregation function is specified
|
||||
"""
|
||||
# Fast path for single value
|
||||
if len(values) == 1:
|
||||
return values['value'].iloc[0]
|
||||
|
||||
if aggr_function == 'lts':
|
||||
return values['value'].iloc[-1]
|
||||
|
||||
# Remove NaN values without inplace operation
|
||||
clean_values = values['value'].dropna()
|
||||
|
||||
if clean_values.empty:
|
||||
return None
|
||||
|
||||
# Use dictionary lookup for aggregation functions (faster than if-elif chain)
|
||||
aggregation_map = {
|
||||
'lts': lambda x: x.iloc[-1],
|
||||
'avg': lambda x: x.mean(),
|
||||
'mdn': lambda x: x.median(),
|
||||
'max': lambda x: x.max(),
|
||||
'min': lambda x: x.min(),
|
||||
}
|
||||
|
||||
if aggr_function in aggregation_map:
|
||||
return aggregation_map[aggr_function](clean_values)
|
||||
else:
|
||||
await self.send_notification_async(
|
||||
if aggr_function not in aggregation_map:
|
||||
self.send_notification(
|
||||
metadata=metadata,
|
||||
notification_id='AGGREGATION_ISSUES',
|
||||
message=f'Invalid aggregation function: {aggr_function}',
|
||||
@@ -116,8 +101,21 @@ class Gates(SientiaMonitoring):
|
||||
)
|
||||
return 'continue'
|
||||
|
||||
if len(values) == 1:
|
||||
return values['value'].iloc[0]
|
||||
|
||||
if aggr_function == 'lts':
|
||||
return aggregation_map['lts'](values['value'])
|
||||
|
||||
clean_values = values['value'].dropna()
|
||||
|
||||
if clean_values.empty:
|
||||
return None
|
||||
|
||||
return aggregation_map[aggr_function](clean_values)
|
||||
|
||||
@activity.defn(name='aggregate_data')
|
||||
async def aggregate_data(self, input_data: dict[str, Any]) -> dict[Hashable, Any]:
|
||||
def aggregate_data(self, input_data: dict[str, Any]) -> dict[Hashable, Any]:
|
||||
"""
|
||||
Aggregate time-series data by tag and name using specified functions.
|
||||
|
||||
@@ -165,7 +163,7 @@ class Gates(SientiaMonitoring):
|
||||
# Get the latest timestamp (last row since data is sorted)
|
||||
latest_timestamp = group['timestamp'].iloc[-1]
|
||||
|
||||
aggr_value = await self.apply_aggregation(group, aggr_function, metadata)
|
||||
aggr_value = self.apply_aggregation(group, aggr_function, metadata)
|
||||
|
||||
if aggr_value == 'continue':
|
||||
continue
|
||||
@@ -197,7 +195,7 @@ class Gates(SientiaMonitoring):
|
||||
except Exception as e:
|
||||
trace = traceback.format_exc()
|
||||
|
||||
await self.send_notification_async(
|
||||
self.send_notification(
|
||||
metadata=metadata,
|
||||
notification_id='AGGREGATION_ISSUES',
|
||||
message=f'Error aggregating data: {e}',
|
||||
@@ -210,7 +208,7 @@ class Gates(SientiaMonitoring):
|
||||
raise e
|
||||
|
||||
@activity.defn(name='data_quality_gate')
|
||||
async def data_quality_gate(self, input_data: dict[str, Any]) -> dict[Hashable, Any]:
|
||||
def data_quality_gate(self, input_data: dict[str, Any]) -> dict[Hashable, Any]:
|
||||
"""
|
||||
Apply data quality filters to incoming data.
|
||||
|
||||
@@ -255,7 +253,7 @@ class Gates(SientiaMonitoring):
|
||||
|
||||
except Exception as e:
|
||||
trace = traceback.format_exc()
|
||||
await self.send_notification_async(
|
||||
self.send_notification(
|
||||
metadata=metadata,
|
||||
notification_id='DATA_QUALITY_GATE_ISSUES',
|
||||
message=f'Error applying filter {filter_name}: {e}',
|
||||
@@ -273,7 +271,7 @@ class Gates(SientiaMonitoring):
|
||||
message = f'{len(filtered_data)} rows has quality issues: {filter_name}: {policy}'
|
||||
attachment = filtered_data.to_string()
|
||||
|
||||
await self.send_notification_async(
|
||||
self.send_notification(
|
||||
metadata=metadata,
|
||||
notification_id=f'DATA_QUALITY_GATE_ISSUES__{filter_name}',
|
||||
message=message,
|
||||
@@ -290,7 +288,7 @@ class Gates(SientiaMonitoring):
|
||||
return data.to_dict()
|
||||
|
||||
@activity.defn(name='write_metrics')
|
||||
async def write_metrics(self, input_data: dict[str, Any]) -> None:
|
||||
def write_metrics(self, input_data: dict[str, Any]) -> None:
|
||||
"""
|
||||
Write metrics to the database.
|
||||
input_data:
|
||||
|
||||
@@ -12,7 +12,7 @@ with workflow.unsafe.imports_passed_through():
|
||||
from sientia_do.notifications.models import NotificationLevel
|
||||
from sientia_do.observability.logger import Logger
|
||||
from sientia_do.observability.sientia_monitoring import SientiaMonitoring
|
||||
from sientia_do.repository.mongodb_repository import MongoDBRepository
|
||||
from sientia_do.repository.mongodb_repository_sync import MongoDBRepository
|
||||
from sientia_do.temporal.constants import DATETIME_FORMAT_MS_WITH_TZ
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ class MongoDB(SientiaMonitoring):
|
||||
self.close()
|
||||
|
||||
@activity.defn(name='load_latest_data')
|
||||
async def load_latest_data(self, input_data: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
def load_latest_data(self, input_data: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Load the latest data from MongoDB collection since a specified timestamp.
|
||||
|
||||
@@ -123,7 +123,7 @@ class MongoDB(SientiaMonitoring):
|
||||
|
||||
self.debug(f'Data filter: {data_filter}', metadata=metadata)
|
||||
|
||||
data = await self.mongodb_repository.find(
|
||||
data = self.mongodb_repository.find(
|
||||
collection_name=collection_name,
|
||||
filters=data_filter,
|
||||
metadata=metadata,
|
||||
@@ -143,7 +143,7 @@ class MongoDB(SientiaMonitoring):
|
||||
return data
|
||||
except Exception as e:
|
||||
trace = traceback.format_exc()
|
||||
await self.send_notification_async(
|
||||
self.send_notification(
|
||||
metadata=metadata,
|
||||
notification_id='MONGO_LOAD_ERROR',
|
||||
message=f'Error loading data from MongoDB: {e}',
|
||||
|
||||
@@ -11,7 +11,7 @@ with workflow.unsafe.imports_passed_through():
|
||||
from sientia_do.notifications.models import NotificationLevel
|
||||
from sientia_do.observability.logger import Logger
|
||||
from sientia_do.observability.sientia_monitoring import SientiaMonitoring
|
||||
from sientia_do.repository.redis_repository import RedisRepository
|
||||
from sientia_do.repository.redis_repository_sync import RedisRepository
|
||||
from sientia_do.temporal.constants import DATETIME_FORMAT, now
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ class Redis(SientiaMonitoring):
|
||||
SientiaMonitoring.shutdown(self)
|
||||
|
||||
@activity.defn(name='get_last_data_timestamp')
|
||||
async def get_last_data_timestamp(self, input_data: dict[str, Any]) -> str | None:
|
||||
def get_last_data_timestamp(self, input_data: dict[str, Any]) -> str | None:
|
||||
"""
|
||||
Retrieve the last processed data timestamp from Redis.
|
||||
|
||||
@@ -97,9 +97,9 @@ class Redis(SientiaMonitoring):
|
||||
self.info(f'Getting last data timestamp for {key}', metadata=metadata)
|
||||
|
||||
try:
|
||||
data_hold = await self.redis_repository.get(key, metadata=metadata)
|
||||
data_hold = self.redis_repository.get(key, metadata=metadata)
|
||||
except Exception as e:
|
||||
await self.send_notification_async(
|
||||
self.send_notification(
|
||||
metadata=metadata,
|
||||
notification_id='REDIS_GET_ERROR',
|
||||
message=f'Error getting last data timestamp: {e}',
|
||||
@@ -117,7 +117,7 @@ class Redis(SientiaMonitoring):
|
||||
return data_hold
|
||||
|
||||
@activity.defn(name='put_last_data_timestamp')
|
||||
async def put_last_data_timestamp(self, input_data: dict[str, Any]) -> str | None:
|
||||
def put_last_data_timestamp(self, input_data: dict[str, Any]) -> str | None:
|
||||
"""
|
||||
Store the last processed data timestamp in Redis.
|
||||
|
||||
@@ -155,11 +155,9 @@ class Redis(SientiaMonitoring):
|
||||
self.info(f'Last collected timestamp to insert: {last_data_timestamp}', metadata=metadata)
|
||||
|
||||
try:
|
||||
await self.redis_repository.set(
|
||||
key, last_data_timestamp, ttl=60 * 60 * 5, metadata=metadata
|
||||
)
|
||||
self.redis_repository.set(key, last_data_timestamp, ttl=60 * 60 * 5, metadata=metadata)
|
||||
except Exception as e:
|
||||
await self.send_notification_async(
|
||||
self.send_notification(
|
||||
metadata=metadata,
|
||||
notification_id='REDIS_SET_ERROR',
|
||||
message=f'Error setting last data timestamp: {e}',
|
||||
@@ -172,7 +170,7 @@ class Redis(SientiaMonitoring):
|
||||
return last_data_timestamp
|
||||
|
||||
@activity.defn(name='group_and_hold_data')
|
||||
async def group_and_hold_data(self, input_data: dict[str, Any]) -> dict[Hashable, Any]:
|
||||
def group_and_hold_data(self, input_data: dict[str, Any]) -> dict[Hashable, Any]:
|
||||
"""
|
||||
Group data by tags and store temporarily in Redis with TTL.
|
||||
|
||||
@@ -210,9 +208,9 @@ class Redis(SientiaMonitoring):
|
||||
self.info(f'Getting held data for {key}', metadata=metadata)
|
||||
|
||||
try:
|
||||
data_hold = await self.redis_repository.get(key, metadata=metadata)
|
||||
data_hold = self.redis_repository.get(key, metadata=metadata)
|
||||
except Exception as e:
|
||||
await self.send_notification_async(
|
||||
self.send_notification(
|
||||
metadata=metadata,
|
||||
notification_id='REDIS_GET_ERROR',
|
||||
message=f'Error getting held data: {e}',
|
||||
@@ -254,7 +252,7 @@ class Redis(SientiaMonitoring):
|
||||
data['timestamp'].max() if not data.empty else data_hold['timestamp']
|
||||
)
|
||||
|
||||
await self.redis_repository.set(key, data_hold, ttl=retention_time, metadata=metadata)
|
||||
self.redis_repository.set(key, data_hold, ttl=retention_time, metadata=metadata)
|
||||
|
||||
data_hold_df = DataFrame(data_hold, index=[0])
|
||||
data_hold_melted = data_hold_df.melt(
|
||||
@@ -264,7 +262,7 @@ class Redis(SientiaMonitoring):
|
||||
|
||||
data_hold_melted.reset_index(drop=True, inplace=True)
|
||||
except Exception as e:
|
||||
await self.send_notification_async(
|
||||
self.send_notification(
|
||||
metadata=metadata,
|
||||
notification_id='REDIS_SET_ERROR',
|
||||
message=f'Error setting held data: {e}',
|
||||
@@ -281,7 +279,7 @@ class Redis(SientiaMonitoring):
|
||||
return data_hold_melted.to_dict()
|
||||
|
||||
@activity.defn(name='store_data_package')
|
||||
async def store_data_package(self, input_data: dict[str, Any]):
|
||||
def store_data_package(self, input_data: dict[str, Any]):
|
||||
"""
|
||||
Stores the data package in redis. It's a debug feature and must be toggled on.
|
||||
input_data:
|
||||
@@ -300,9 +298,9 @@ class Redis(SientiaMonitoring):
|
||||
cache = {'data': data.to_dict(), 'held_data': held_data.to_dict()}
|
||||
|
||||
try:
|
||||
await self.redis_repository.set(key, cache, ttl=120, metadata=metadata)
|
||||
self.redis_repository.set(key, cache, ttl=120, metadata=metadata)
|
||||
except Exception as e:
|
||||
await self.send_notification_async(
|
||||
self.send_notification(
|
||||
metadata=metadata,
|
||||
notification_id='REDIS_SET_ERROR',
|
||||
message=f'Error setting data package: {e}',
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from sientia_do.observability.logger import Logger
|
||||
from temporalio.client import Client
|
||||
from temporalio.worker import PollerBehaviorAutoscaling, Worker
|
||||
|
||||
# Worker configuration parameters with default values
|
||||
# See worker_parameters.md for detailed documentation
|
||||
parameters = [
|
||||
('MAX_CONCURRENT_WORKFLOW_TASKS', '200'),
|
||||
('MAX_CONCURRENT_ACTIVITIES', '200'),
|
||||
('MAX_CONCURRENT_LOCAL_ACTIVITIES', '200'),
|
||||
('MAX_CACHED_WORKFLOWS', '200'),
|
||||
('WORKFLOW_POLLER_BEHAVIOUR_MINIMUM', '10'),
|
||||
('WORKFLOW_POLLER_BEHAVIOUR_INITIAL', '100'),
|
||||
('WORKFLOW_POLLER_BEHAVIOUR_MAXIMUM', '200'),
|
||||
('ACTIVITY_POLLER_BEHAVIOUR_MINIMUM', '10'),
|
||||
('ACTIVITY_POLLER_BEHAVIOUR_INITIAL', '100'),
|
||||
('ACTIVITY_POLLER_BEHAVIOUR_MAXIMUM', '200'),
|
||||
]
|
||||
|
||||
|
||||
def camel_to_snake(text: str) -> str:
|
||||
"""Convert camelCase or PascalCase to snake_case."""
|
||||
text = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', text)
|
||||
text = re.sub('([a-z0-9])([A-Z])', r'\1_\2', text)
|
||||
return text.lower()
|
||||
|
||||
|
||||
def prepare_worker(
|
||||
main_workflow: type,
|
||||
other_workflows: Sequence[type],
|
||||
activities: Sequence[Any],
|
||||
temporal_client: Client,
|
||||
logger: Logger,
|
||||
) -> Worker:
|
||||
main_workflow_name = main_workflow.__name__.upper()
|
||||
|
||||
queue_name = f'{camel_to_snake(main_workflow.__name__)}-queue'
|
||||
|
||||
local_workflow_parameters = {}
|
||||
|
||||
for parameter in parameters:
|
||||
local_workflow_parameters[parameter[0]] = int(
|
||||
os.getenv(main_workflow_name + '_' + parameter[0], parameter[1])
|
||||
)
|
||||
|
||||
logger.info(f'Preparing worker for {main_workflow_name} with queue {queue_name}')
|
||||
|
||||
return Worker(
|
||||
temporal_client,
|
||||
task_queue=queue_name,
|
||||
workflows=[main_workflow, *other_workflows],
|
||||
activities=[*activities],
|
||||
max_concurrent_workflow_tasks=local_workflow_parameters['MAX_CONCURRENT_WORKFLOW_TASKS'],
|
||||
max_concurrent_activities=local_workflow_parameters['MAX_CONCURRENT_ACTIVITIES'],
|
||||
max_concurrent_local_activities=local_workflow_parameters[
|
||||
'MAX_CONCURRENT_LOCAL_ACTIVITIES'
|
||||
],
|
||||
max_cached_workflows=local_workflow_parameters['MAX_CACHED_WORKFLOWS'],
|
||||
workflow_task_poller_behavior=PollerBehaviorAutoscaling(
|
||||
minimum=local_workflow_parameters['WORKFLOW_POLLER_BEHAVIOUR_MINIMUM'],
|
||||
initial=local_workflow_parameters['WORKFLOW_POLLER_BEHAVIOUR_INITIAL'],
|
||||
maximum=local_workflow_parameters['WORKFLOW_POLLER_BEHAVIOUR_MAXIMUM'],
|
||||
),
|
||||
activity_task_poller_behavior=PollerBehaviorAutoscaling(
|
||||
minimum=local_workflow_parameters['ACTIVITY_POLLER_BEHAVIOUR_MINIMUM'],
|
||||
initial=local_workflow_parameters['ACTIVITY_POLLER_BEHAVIOUR_INITIAL'],
|
||||
maximum=local_workflow_parameters['ACTIVITY_POLLER_BEHAVIOUR_MAXIMUM'],
|
||||
),
|
||||
)
|
||||
@@ -9,6 +9,7 @@ with workflow.unsafe.imports_passed_through():
|
||||
from prometheus_client import start_http_server
|
||||
from sientia_do.notifications.handlers import CoreNotificationHandler as NotificationHandler
|
||||
from sientia_do.observability.logger import get_logger
|
||||
from sientia_do.temporal.worker.prepare_worker import prepare_worker
|
||||
from sientia_do.utils.connectors_config import (
|
||||
build_api_config,
|
||||
build_mongodb_config,
|
||||
@@ -18,7 +19,6 @@ with workflow.unsafe.imports_passed_through():
|
||||
|
||||
from scouter import metrics
|
||||
from scouter.activities.activities import Activities
|
||||
from scouter.worker.prepare_worker import prepare_worker
|
||||
from scouter.workflow.pi_web_api_scouter import PIWebAPIScouter
|
||||
from scouter.workflow.scouter import Scouter
|
||||
from scouter.workflow.sub_workflows.core_scouter import CoreScouter
|
||||
@@ -28,26 +28,6 @@ POD_ID = os.getenv('HOSTNAME', 'localhost')
|
||||
SDK_METRICS_PORT = int(os.getenv('HTTP_SDK_METRICS_PORT', '9091'))
|
||||
|
||||
|
||||
# For optmized latency, Temporal docs recommends fixed slots, ensuring
|
||||
# high concurency levels.
|
||||
|
||||
MAX_CONCURRENT_WORKFLOW_TASKS = int(os.getenv('MAX_CONCURRENT_WORKFLOW_TASKS', '200'))
|
||||
MAX_CONCURRENT_ACTIVITIES = int(os.getenv('MAX_CONCURRENT_ACTIVITIES', '200'))
|
||||
MAX_CONCURRENT_LOCAL_ACTIVITIES = int(os.getenv('MAX_CONCURRENT_LOCAL_ACTIVITIES', '200'))
|
||||
MAX_CACHED_WORKFLOWS = int(os.getenv('MAX_CACHED_WORKFLOWS', '200'))
|
||||
|
||||
|
||||
# Temporal docs also recommends an autoscaling policy, with agrresive limits to prioritize latency over throughput.
|
||||
|
||||
WORKFLOW_POLLER_BEHAVIOUR_MINIMUM = int(os.getenv('WORKFLOW_POLLER_BEHAVIOUR_MINIMUM', '10'))
|
||||
WORKFLOW_POLLER_BEHAVIOUR_INITIAL = int(os.getenv('WORKFLOW_POLLER_BEHAVIOUR_INITIAL', '100'))
|
||||
WORKFLOW_POLLER_BEHAVIOUR_MAXIMUM = int(os.getenv('WORKFLOW_POLLER_BEHAVIOUR_MAXIMUM', '200'))
|
||||
|
||||
ACTIVITY_POLLER_BEHAVIOUR_MINIMUM = int(os.getenv('ACTIVITY_POLLER_BEHAVIOUR_MINIMUM', '10'))
|
||||
ACTIVITY_POLLER_BEHAVIOUR_INITIAL = int(os.getenv('ACTIVITY_POLLER_BEHAVIOUR_INITIAL', '100'))
|
||||
ACTIVITY_POLLER_BEHAVIOUR_MAXIMUM = int(os.getenv('ACTIVITY_POLLER_BEHAVIOUR_MAXIMUM', '200'))
|
||||
|
||||
|
||||
async def main():
|
||||
"""
|
||||
Main entry point for the Scouter Temporal worker.
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
# Worker Parameters Documentation
|
||||
|
||||
This document explains each configuration parameter used in the `prepare_worker.py` file for configuring Temporal workers.
|
||||
|
||||
## Overview
|
||||
|
||||
All parameters can be configured via environment variables using the pattern: `{WORKFLOW_NAME}_{PARAMETER_NAME}`. If not set, default values are used as specified below.
|
||||
|
||||
## Concurrency Parameters
|
||||
|
||||
### MAX_CONCURRENT_WORKFLOW_TASKS
|
||||
- **Default**: `200`
|
||||
- **Description**: Maximum number of concurrent workflow tasks that can be processed simultaneously by the worker. This controls how many workflow executions can be actively running at the same time.
|
||||
- **Usage**: Set via `max_concurrent_workflow_tasks` in the Worker configuration.
|
||||
- **Impact**: Higher values allow more workflows to run concurrently but consume more resources. Lower values provide better resource control but may limit throughput.
|
||||
|
||||
### MAX_CONCURRENT_ACTIVITIES
|
||||
- **Default**: `200`
|
||||
- **Description**: Maximum number of concurrent activity tasks that can be executed simultaneously by the worker. Activities are the actual work units that perform business logic.
|
||||
- **Usage**: Set via `max_concurrent_activities` in the Worker configuration.
|
||||
- **Impact**: Controls the parallelism of activity execution. Higher values increase throughput but require more system resources (CPU, memory, network connections).
|
||||
|
||||
### MAX_CONCURRENT_LOCAL_ACTIVITIES
|
||||
- **Default**: `200`
|
||||
- **Description**: Maximum number of concurrent local activity tasks that can be executed simultaneously. Local activities run in the same process as the workflow, without requiring a separate activity worker.
|
||||
- **Usage**: Set via `max_concurrent_local_activities` in the Worker configuration.
|
||||
- **Impact**: Similar to regular activities, but local activities have lower latency and overhead since they don't require network round-trips. Useful for lightweight operations.
|
||||
|
||||
## Caching Parameters
|
||||
|
||||
### MAX_CACHED_WORKFLOWS
|
||||
- **Default**: `200`
|
||||
- **Description**: Maximum number of workflow instances that can be cached in memory by the worker. Cached workflows allow faster resumption of execution without reloading state.
|
||||
- **Usage**: Set via `max_cached_workflows` in the Worker configuration.
|
||||
- **Impact**: Higher values improve performance for frequently accessed workflows but consume more memory. Lower values reduce memory usage but may require more frequent state reloads.
|
||||
|
||||
## Understanding Pollers in Temporal
|
||||
|
||||
**Pollers** are components of Temporal Workers that continuously request tasks from the Temporal service's Task Queues via synchronous RPCs. There are separate pollers for workflow tasks and activity tasks.
|
||||
|
||||
### How Pollers Work
|
||||
|
||||
Pollers send requests to the Temporal service to retrieve tasks from Task Queues. When a task is available, the poller retrieves it and the Worker processes it using registered Workflow or Activity handlers. This architecture provides:
|
||||
- **Load Balancing**: Workers only poll when they have capacity, distributing load across multiple processes
|
||||
- **Fault Tolerance**: Tasks persist in queues if a Worker fails, allowing recovery
|
||||
- **Task Routing**: Tasks can be routed to specific Worker processes
|
||||
|
||||
### Autoscaling Poller Behavior
|
||||
|
||||
Temporal supports autoscaling that dynamically adjusts the number of concurrent pollers based on workload. The system scales up during high load and down during low load, maintaining a baseline for responsiveness. Autoscaling is configured with `minimum`, `initial`, and `maximum` parameters that define the scaling bounds.
|
||||
|
||||
## Workflow Poller Behavior (Autoscaling)
|
||||
|
||||
These parameters control the autoscaling behavior of the workflow task poller, which retrieves workflow tasks from the Temporal server.
|
||||
|
||||
### WORKFLOW_POLLER_BEHAVIOUR_MINIMUM
|
||||
- **Default**: `10`
|
||||
- **Description**: Minimum number of concurrent pollers for workflow tasks. The poller count will never go below this value.
|
||||
- **Usage**: Set via `minimum` in `PollerBehaviorAutoscaling` for `workflow_task_poller_behavior`.
|
||||
- **Impact**: Ensures a baseline level of polling activity even during low load periods.
|
||||
|
||||
### WORKFLOW_POLLER_BEHAVIOUR_INITIAL
|
||||
- **Default**: `100`
|
||||
- **Description**: Initial number of concurrent pollers for workflow tasks when the worker starts.
|
||||
- **Usage**: Set via `initial` in `PollerBehaviorAutoscaling` for `workflow_task_poller_behavior`.
|
||||
- **Impact**: Determines the starting point for poller scaling. Higher values provide faster initial task acquisition but consume more resources.
|
||||
|
||||
### WORKFLOW_POLLER_BEHAVIOUR_MAXIMUM
|
||||
- **Default**: `200`
|
||||
- **Description**: Maximum number of concurrent pollers allowed for workflow tasks. The poller count will not exceed this value even under high load.
|
||||
- **Usage**: Set via `maximum` in `PollerBehaviorAutoscaling` for `workflow_task_poller_behavior`.
|
||||
- **Impact**: Caps the resource consumption for workflow task polling. Prevents excessive polling that could overwhelm the Temporal server or worker.
|
||||
|
||||
## Activity Poller Behavior (Autoscaling)
|
||||
|
||||
These parameters control the autoscaling behavior of the activity task poller, which retrieves activity tasks from the Temporal server.
|
||||
|
||||
### ACTIVITY_POLLER_BEHAVIOUR_MINIMUM
|
||||
- **Default**: `10`
|
||||
- **Description**: Minimum number of concurrent pollers for activity tasks. The poller count will never go below this value.
|
||||
- **Usage**: Set via `minimum` in `PollerBehaviorAutoscaling` for `activity_task_poller_behavior`.
|
||||
- **Impact**: Ensures a baseline level of polling activity even during low load periods.
|
||||
|
||||
### ACTIVITY_POLLER_BEHAVIOUR_INITIAL
|
||||
- **Default**: `100`
|
||||
- **Description**: Initial number of concurrent pollers for activity tasks when the worker starts.
|
||||
- **Usage**: Set via `initial` in `PollerBehaviorAutoscaling` for `activity_task_poller_behavior`.
|
||||
- **Impact**: Determines the starting point for poller scaling. Higher values provide faster initial task acquisition but consume more resources.
|
||||
|
||||
### ACTIVITY_POLLER_BEHAVIOUR_MAXIMUM
|
||||
- **Default**: `200`
|
||||
- **Description**: Maximum number of concurrent pollers allowed for activity tasks. The poller count will not exceed this value even under high load.
|
||||
- **Usage**: Set via `maximum` in `PollerBehaviorAutoscaling` for `activity_task_poller_behavior`.
|
||||
- **Impact**: Caps the resource consumption for activity task polling. Prevents excessive polling that could overwhelm the Temporal server or worker.
|
||||
|
||||
## Configuration Example
|
||||
|
||||
To override these parameters, set environment variables using the pattern:
|
||||
```
|
||||
{WORKFLOW_NAME}_{PARAMETER_NAME}={value}
|
||||
```
|
||||
|
||||
For example, if your workflow is named `ScouterWorkflow`:
|
||||
```bash
|
||||
SCOUTERWORKFLOW_MAX_CONCURRENT_ACTIVITIES=500
|
||||
SCOUTERWORKFLOW_WORKFLOW_POLLER_BEHAVIOUR_MAXIMUM=300
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- All parameter values are converted to integers before use.
|
||||
- The autoscaling poller behavior dynamically adjusts the number of pollers between the minimum and maximum values based on workload.
|
||||
- These parameters should be tuned based on your specific workload characteristics, available resources, and performance requirements.
|
||||
Reference in New Issue
Block a user