aiecs 1.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aiecs/__init__.py +72 -0
- aiecs/__main__.py +41 -0
- aiecs/aiecs_client.py +469 -0
- aiecs/application/__init__.py +10 -0
- aiecs/application/executors/__init__.py +10 -0
- aiecs/application/executors/operation_executor.py +363 -0
- aiecs/application/knowledge_graph/__init__.py +7 -0
- aiecs/application/knowledge_graph/builder/__init__.py +37 -0
- aiecs/application/knowledge_graph/builder/document_builder.py +375 -0
- aiecs/application/knowledge_graph/builder/graph_builder.py +356 -0
- aiecs/application/knowledge_graph/builder/schema_mapping.py +531 -0
- aiecs/application/knowledge_graph/builder/structured_pipeline.py +443 -0
- aiecs/application/knowledge_graph/builder/text_chunker.py +319 -0
- aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
- aiecs/application/knowledge_graph/extractors/base.py +100 -0
- aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +327 -0
- aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +349 -0
- aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +244 -0
- aiecs/application/knowledge_graph/fusion/__init__.py +23 -0
- aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +387 -0
- aiecs/application/knowledge_graph/fusion/entity_linker.py +343 -0
- aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +580 -0
- aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +189 -0
- aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
- aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +344 -0
- aiecs/application/knowledge_graph/pattern_matching/query_executor.py +378 -0
- aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
- aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +199 -0
- aiecs/application/knowledge_graph/profiling/query_profiler.py +223 -0
- aiecs/application/knowledge_graph/reasoning/__init__.py +27 -0
- aiecs/application/knowledge_graph/reasoning/evidence_synthesis.py +347 -0
- aiecs/application/knowledge_graph/reasoning/inference_engine.py +504 -0
- aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +167 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/__init__.py +79 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_builder.py +513 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_nodes.py +630 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +654 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +477 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +390 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +217 -0
- aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +169 -0
- aiecs/application/knowledge_graph/reasoning/query_planner.py +872 -0
- aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +554 -0
- aiecs/application/knowledge_graph/retrieval/__init__.py +19 -0
- aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +596 -0
- aiecs/application/knowledge_graph/search/__init__.py +59 -0
- aiecs/application/knowledge_graph/search/hybrid_search.py +423 -0
- aiecs/application/knowledge_graph/search/reranker.py +295 -0
- aiecs/application/knowledge_graph/search/reranker_strategies.py +553 -0
- aiecs/application/knowledge_graph/search/text_similarity.py +398 -0
- aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
- aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +329 -0
- aiecs/application/knowledge_graph/traversal/path_scorer.py +269 -0
- aiecs/application/knowledge_graph/validators/__init__.py +13 -0
- aiecs/application/knowledge_graph/validators/relation_validator.py +189 -0
- aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
- aiecs/application/knowledge_graph/visualization/graph_visualizer.py +321 -0
- aiecs/common/__init__.py +9 -0
- aiecs/common/knowledge_graph/__init__.py +17 -0
- aiecs/common/knowledge_graph/runnable.py +484 -0
- aiecs/config/__init__.py +16 -0
- aiecs/config/config.py +498 -0
- aiecs/config/graph_config.py +137 -0
- aiecs/config/registry.py +23 -0
- aiecs/core/__init__.py +46 -0
- aiecs/core/interface/__init__.py +34 -0
- aiecs/core/interface/execution_interface.py +152 -0
- aiecs/core/interface/storage_interface.py +171 -0
- aiecs/domain/__init__.py +289 -0
- aiecs/domain/agent/__init__.py +189 -0
- aiecs/domain/agent/base_agent.py +697 -0
- aiecs/domain/agent/exceptions.py +103 -0
- aiecs/domain/agent/graph_aware_mixin.py +559 -0
- aiecs/domain/agent/hybrid_agent.py +490 -0
- aiecs/domain/agent/integration/__init__.py +26 -0
- aiecs/domain/agent/integration/context_compressor.py +222 -0
- aiecs/domain/agent/integration/context_engine_adapter.py +252 -0
- aiecs/domain/agent/integration/retry_policy.py +219 -0
- aiecs/domain/agent/integration/role_config.py +213 -0
- aiecs/domain/agent/knowledge_aware_agent.py +646 -0
- aiecs/domain/agent/lifecycle.py +296 -0
- aiecs/domain/agent/llm_agent.py +300 -0
- aiecs/domain/agent/memory/__init__.py +12 -0
- aiecs/domain/agent/memory/conversation.py +197 -0
- aiecs/domain/agent/migration/__init__.py +14 -0
- aiecs/domain/agent/migration/conversion.py +160 -0
- aiecs/domain/agent/migration/legacy_wrapper.py +90 -0
- aiecs/domain/agent/models.py +317 -0
- aiecs/domain/agent/observability.py +407 -0
- aiecs/domain/agent/persistence.py +289 -0
- aiecs/domain/agent/prompts/__init__.py +29 -0
- aiecs/domain/agent/prompts/builder.py +161 -0
- aiecs/domain/agent/prompts/formatters.py +189 -0
- aiecs/domain/agent/prompts/template.py +255 -0
- aiecs/domain/agent/registry.py +260 -0
- aiecs/domain/agent/tool_agent.py +257 -0
- aiecs/domain/agent/tools/__init__.py +12 -0
- aiecs/domain/agent/tools/schema_generator.py +221 -0
- aiecs/domain/community/__init__.py +155 -0
- aiecs/domain/community/agent_adapter.py +477 -0
- aiecs/domain/community/analytics.py +481 -0
- aiecs/domain/community/collaborative_workflow.py +642 -0
- aiecs/domain/community/communication_hub.py +645 -0
- aiecs/domain/community/community_builder.py +320 -0
- aiecs/domain/community/community_integration.py +800 -0
- aiecs/domain/community/community_manager.py +813 -0
- aiecs/domain/community/decision_engine.py +879 -0
- aiecs/domain/community/exceptions.py +225 -0
- aiecs/domain/community/models/__init__.py +33 -0
- aiecs/domain/community/models/community_models.py +268 -0
- aiecs/domain/community/resource_manager.py +457 -0
- aiecs/domain/community/shared_context_manager.py +603 -0
- aiecs/domain/context/__init__.py +58 -0
- aiecs/domain/context/context_engine.py +989 -0
- aiecs/domain/context/conversation_models.py +354 -0
- aiecs/domain/context/graph_memory.py +467 -0
- aiecs/domain/execution/__init__.py +12 -0
- aiecs/domain/execution/model.py +57 -0
- aiecs/domain/knowledge_graph/__init__.py +19 -0
- aiecs/domain/knowledge_graph/models/__init__.py +52 -0
- aiecs/domain/knowledge_graph/models/entity.py +130 -0
- aiecs/domain/knowledge_graph/models/evidence.py +194 -0
- aiecs/domain/knowledge_graph/models/inference_rule.py +186 -0
- aiecs/domain/knowledge_graph/models/path.py +179 -0
- aiecs/domain/knowledge_graph/models/path_pattern.py +173 -0
- aiecs/domain/knowledge_graph/models/query.py +272 -0
- aiecs/domain/knowledge_graph/models/query_plan.py +187 -0
- aiecs/domain/knowledge_graph/models/relation.py +136 -0
- aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
- aiecs/domain/knowledge_graph/schema/entity_type.py +135 -0
- aiecs/domain/knowledge_graph/schema/graph_schema.py +271 -0
- aiecs/domain/knowledge_graph/schema/property_schema.py +155 -0
- aiecs/domain/knowledge_graph/schema/relation_type.py +171 -0
- aiecs/domain/knowledge_graph/schema/schema_manager.py +496 -0
- aiecs/domain/knowledge_graph/schema/type_enums.py +205 -0
- aiecs/domain/task/__init__.py +13 -0
- aiecs/domain/task/dsl_processor.py +613 -0
- aiecs/domain/task/model.py +62 -0
- aiecs/domain/task/task_context.py +268 -0
- aiecs/infrastructure/__init__.py +24 -0
- aiecs/infrastructure/graph_storage/__init__.py +11 -0
- aiecs/infrastructure/graph_storage/base.py +601 -0
- aiecs/infrastructure/graph_storage/batch_operations.py +449 -0
- aiecs/infrastructure/graph_storage/cache.py +429 -0
- aiecs/infrastructure/graph_storage/distributed.py +226 -0
- aiecs/infrastructure/graph_storage/error_handling.py +390 -0
- aiecs/infrastructure/graph_storage/graceful_degradation.py +306 -0
- aiecs/infrastructure/graph_storage/health_checks.py +378 -0
- aiecs/infrastructure/graph_storage/in_memory.py +514 -0
- aiecs/infrastructure/graph_storage/index_optimization.py +483 -0
- aiecs/infrastructure/graph_storage/lazy_loading.py +410 -0
- aiecs/infrastructure/graph_storage/metrics.py +357 -0
- aiecs/infrastructure/graph_storage/migration.py +413 -0
- aiecs/infrastructure/graph_storage/pagination.py +471 -0
- aiecs/infrastructure/graph_storage/performance_monitoring.py +466 -0
- aiecs/infrastructure/graph_storage/postgres.py +871 -0
- aiecs/infrastructure/graph_storage/query_optimizer.py +635 -0
- aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
- aiecs/infrastructure/graph_storage/sqlite.py +623 -0
- aiecs/infrastructure/graph_storage/streaming.py +495 -0
- aiecs/infrastructure/messaging/__init__.py +13 -0
- aiecs/infrastructure/messaging/celery_task_manager.py +383 -0
- aiecs/infrastructure/messaging/websocket_manager.py +298 -0
- aiecs/infrastructure/monitoring/__init__.py +34 -0
- aiecs/infrastructure/monitoring/executor_metrics.py +174 -0
- aiecs/infrastructure/monitoring/global_metrics_manager.py +213 -0
- aiecs/infrastructure/monitoring/structured_logger.py +48 -0
- aiecs/infrastructure/monitoring/tracing_manager.py +410 -0
- aiecs/infrastructure/persistence/__init__.py +24 -0
- aiecs/infrastructure/persistence/context_engine_client.py +187 -0
- aiecs/infrastructure/persistence/database_manager.py +333 -0
- aiecs/infrastructure/persistence/file_storage.py +754 -0
- aiecs/infrastructure/persistence/redis_client.py +220 -0
- aiecs/llm/__init__.py +86 -0
- aiecs/llm/callbacks/__init__.py +11 -0
- aiecs/llm/callbacks/custom_callbacks.py +264 -0
- aiecs/llm/client_factory.py +420 -0
- aiecs/llm/clients/__init__.py +33 -0
- aiecs/llm/clients/base_client.py +193 -0
- aiecs/llm/clients/googleai_client.py +181 -0
- aiecs/llm/clients/openai_client.py +131 -0
- aiecs/llm/clients/vertex_client.py +437 -0
- aiecs/llm/clients/xai_client.py +184 -0
- aiecs/llm/config/__init__.py +51 -0
- aiecs/llm/config/config_loader.py +275 -0
- aiecs/llm/config/config_validator.py +236 -0
- aiecs/llm/config/model_config.py +151 -0
- aiecs/llm/utils/__init__.py +10 -0
- aiecs/llm/utils/validate_config.py +91 -0
- aiecs/main.py +363 -0
- aiecs/scripts/__init__.py +3 -0
- aiecs/scripts/aid/VERSION_MANAGEMENT.md +97 -0
- aiecs/scripts/aid/__init__.py +19 -0
- aiecs/scripts/aid/version_manager.py +215 -0
- aiecs/scripts/dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md +242 -0
- aiecs/scripts/dependance_check/README_DEPENDENCY_CHECKER.md +310 -0
- aiecs/scripts/dependance_check/__init__.py +17 -0
- aiecs/scripts/dependance_check/dependency_checker.py +938 -0
- aiecs/scripts/dependance_check/dependency_fixer.py +391 -0
- aiecs/scripts/dependance_check/download_nlp_data.py +396 -0
- aiecs/scripts/dependance_check/quick_dependency_check.py +270 -0
- aiecs/scripts/dependance_check/setup_nlp_data.sh +217 -0
- aiecs/scripts/dependance_patch/__init__.py +7 -0
- aiecs/scripts/dependance_patch/fix_weasel/README_WEASEL_PATCH.md +126 -0
- aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
- aiecs/scripts/dependance_patch/fix_weasel/fix_weasel_validator.py +128 -0
- aiecs/scripts/dependance_patch/fix_weasel/fix_weasel_validator.sh +82 -0
- aiecs/scripts/dependance_patch/fix_weasel/patch_weasel_library.sh +188 -0
- aiecs/scripts/dependance_patch/fix_weasel/run_weasel_patch.sh +41 -0
- aiecs/scripts/tools_develop/README.md +449 -0
- aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
- aiecs/scripts/tools_develop/__init__.py +21 -0
- aiecs/scripts/tools_develop/check_type_annotations.py +259 -0
- aiecs/scripts/tools_develop/validate_tool_schemas.py +422 -0
- aiecs/scripts/tools_develop/verify_tools.py +356 -0
- aiecs/tasks/__init__.py +1 -0
- aiecs/tasks/worker.py +172 -0
- aiecs/tools/__init__.py +299 -0
- aiecs/tools/apisource/__init__.py +99 -0
- aiecs/tools/apisource/intelligence/__init__.py +19 -0
- aiecs/tools/apisource/intelligence/data_fusion.py +381 -0
- aiecs/tools/apisource/intelligence/query_analyzer.py +413 -0
- aiecs/tools/apisource/intelligence/search_enhancer.py +388 -0
- aiecs/tools/apisource/monitoring/__init__.py +9 -0
- aiecs/tools/apisource/monitoring/metrics.py +303 -0
- aiecs/tools/apisource/providers/__init__.py +115 -0
- aiecs/tools/apisource/providers/base.py +664 -0
- aiecs/tools/apisource/providers/census.py +401 -0
- aiecs/tools/apisource/providers/fred.py +564 -0
- aiecs/tools/apisource/providers/newsapi.py +412 -0
- aiecs/tools/apisource/providers/worldbank.py +357 -0
- aiecs/tools/apisource/reliability/__init__.py +12 -0
- aiecs/tools/apisource/reliability/error_handler.py +375 -0
- aiecs/tools/apisource/reliability/fallback_strategy.py +391 -0
- aiecs/tools/apisource/tool.py +850 -0
- aiecs/tools/apisource/utils/__init__.py +9 -0
- aiecs/tools/apisource/utils/validators.py +338 -0
- aiecs/tools/base_tool.py +201 -0
- aiecs/tools/docs/__init__.py +121 -0
- aiecs/tools/docs/ai_document_orchestrator.py +599 -0
- aiecs/tools/docs/ai_document_writer_orchestrator.py +2403 -0
- aiecs/tools/docs/content_insertion_tool.py +1333 -0
- aiecs/tools/docs/document_creator_tool.py +1317 -0
- aiecs/tools/docs/document_layout_tool.py +1166 -0
- aiecs/tools/docs/document_parser_tool.py +994 -0
- aiecs/tools/docs/document_writer_tool.py +1818 -0
- aiecs/tools/knowledge_graph/__init__.py +17 -0
- aiecs/tools/knowledge_graph/graph_reasoning_tool.py +734 -0
- aiecs/tools/knowledge_graph/graph_search_tool.py +923 -0
- aiecs/tools/knowledge_graph/kg_builder_tool.py +476 -0
- aiecs/tools/langchain_adapter.py +542 -0
- aiecs/tools/schema_generator.py +275 -0
- aiecs/tools/search_tool/__init__.py +100 -0
- aiecs/tools/search_tool/analyzers.py +589 -0
- aiecs/tools/search_tool/cache.py +260 -0
- aiecs/tools/search_tool/constants.py +128 -0
- aiecs/tools/search_tool/context.py +216 -0
- aiecs/tools/search_tool/core.py +749 -0
- aiecs/tools/search_tool/deduplicator.py +123 -0
- aiecs/tools/search_tool/error_handler.py +271 -0
- aiecs/tools/search_tool/metrics.py +371 -0
- aiecs/tools/search_tool/rate_limiter.py +178 -0
- aiecs/tools/search_tool/schemas.py +277 -0
- aiecs/tools/statistics/__init__.py +80 -0
- aiecs/tools/statistics/ai_data_analysis_orchestrator.py +643 -0
- aiecs/tools/statistics/ai_insight_generator_tool.py +505 -0
- aiecs/tools/statistics/ai_report_orchestrator_tool.py +694 -0
- aiecs/tools/statistics/data_loader_tool.py +564 -0
- aiecs/tools/statistics/data_profiler_tool.py +658 -0
- aiecs/tools/statistics/data_transformer_tool.py +573 -0
- aiecs/tools/statistics/data_visualizer_tool.py +495 -0
- aiecs/tools/statistics/model_trainer_tool.py +487 -0
- aiecs/tools/statistics/statistical_analyzer_tool.py +459 -0
- aiecs/tools/task_tools/__init__.py +86 -0
- aiecs/tools/task_tools/chart_tool.py +732 -0
- aiecs/tools/task_tools/classfire_tool.py +922 -0
- aiecs/tools/task_tools/image_tool.py +447 -0
- aiecs/tools/task_tools/office_tool.py +684 -0
- aiecs/tools/task_tools/pandas_tool.py +635 -0
- aiecs/tools/task_tools/report_tool.py +635 -0
- aiecs/tools/task_tools/research_tool.py +392 -0
- aiecs/tools/task_tools/scraper_tool.py +715 -0
- aiecs/tools/task_tools/stats_tool.py +688 -0
- aiecs/tools/temp_file_manager.py +130 -0
- aiecs/tools/tool_executor/__init__.py +37 -0
- aiecs/tools/tool_executor/tool_executor.py +881 -0
- aiecs/utils/LLM_output_structor.py +445 -0
- aiecs/utils/__init__.py +34 -0
- aiecs/utils/base_callback.py +47 -0
- aiecs/utils/cache_provider.py +695 -0
- aiecs/utils/execution_utils.py +184 -0
- aiecs/utils/logging.py +1 -0
- aiecs/utils/prompt_loader.py +14 -0
- aiecs/utils/token_usage_repository.py +323 -0
- aiecs/ws/__init__.py +0 -0
- aiecs/ws/socket_server.py +52 -0
- aiecs-1.5.1.dist-info/METADATA +608 -0
- aiecs-1.5.1.dist-info/RECORD +302 -0
- aiecs-1.5.1.dist-info/WHEEL +5 -0
- aiecs-1.5.1.dist-info/entry_points.txt +10 -0
- aiecs-1.5.1.dist-info/licenses/LICENSE +225 -0
- aiecs-1.5.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PostgreSQL Index Optimization
|
|
3
|
+
|
|
4
|
+
Provides index analysis, recommendations, and automated index creation
|
|
5
|
+
based on query patterns and workload analysis.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import asyncpg
|
|
10
|
+
from typing import List, Dict, Any, Optional
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class IndexRecommendation:
|
|
18
|
+
"""Index creation recommendation"""
|
|
19
|
+
|
|
20
|
+
table_name: str
|
|
21
|
+
columns: List[str]
|
|
22
|
+
index_type: str # btree, gin, gist, ivfflat
|
|
23
|
+
reason: str
|
|
24
|
+
estimated_benefit: str # "high", "medium", "low"
|
|
25
|
+
create_sql: str
|
|
26
|
+
|
|
27
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
28
|
+
"""Convert to dictionary"""
|
|
29
|
+
return {
|
|
30
|
+
"table": self.table_name,
|
|
31
|
+
"columns": self.columns,
|
|
32
|
+
"type": self.index_type,
|
|
33
|
+
"reason": self.reason,
|
|
34
|
+
"benefit": self.estimated_benefit,
|
|
35
|
+
"sql": self.create_sql,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class IndexInfo:
|
|
41
|
+
"""Information about an existing index"""
|
|
42
|
+
|
|
43
|
+
index_name: str
|
|
44
|
+
table_name: str
|
|
45
|
+
columns: List[str]
|
|
46
|
+
index_type: str
|
|
47
|
+
is_unique: bool
|
|
48
|
+
size_bytes: int
|
|
49
|
+
usage_count: int = 0
|
|
50
|
+
|
|
51
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
52
|
+
"""Convert to dictionary"""
|
|
53
|
+
return {
|
|
54
|
+
"name": self.index_name,
|
|
55
|
+
"table": self.table_name,
|
|
56
|
+
"columns": self.columns,
|
|
57
|
+
"type": self.index_type,
|
|
58
|
+
"unique": self.is_unique,
|
|
59
|
+
"size_mb": round(self.size_bytes / (1024 * 1024), 2),
|
|
60
|
+
"usage_count": self.usage_count,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class IndexOptimizer:
|
|
65
|
+
"""
|
|
66
|
+
Analyze and optimize PostgreSQL indexes for graph storage
|
|
67
|
+
|
|
68
|
+
Provides:
|
|
69
|
+
- Index analysis and usage statistics
|
|
70
|
+
- Query pattern analysis
|
|
71
|
+
- Index recommendations
|
|
72
|
+
- Automated index creation
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
```python
|
|
76
|
+
optimizer = IndexOptimizer(conn)
|
|
77
|
+
|
|
78
|
+
# Analyze existing indexes
|
|
79
|
+
indexes = await optimizer.analyze_indexes()
|
|
80
|
+
|
|
81
|
+
# Get recommendations
|
|
82
|
+
recommendations = await optimizer.get_recommendations()
|
|
83
|
+
for rec in recommendations:
|
|
84
|
+
print(rec.create_sql)
|
|
85
|
+
|
|
86
|
+
# Apply recommendations
|
|
87
|
+
await optimizer.apply_recommendations(recommendations)
|
|
88
|
+
```
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def __init__(self, pool: asyncpg.Pool):
|
|
92
|
+
"""
|
|
93
|
+
Initialize index optimizer
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
pool: PostgreSQL connection pool
|
|
97
|
+
"""
|
|
98
|
+
self.pool = pool
|
|
99
|
+
|
|
100
|
+
async def analyze_indexes(self) -> List[IndexInfo]:
|
|
101
|
+
"""
|
|
102
|
+
Analyze all indexes on graph tables
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
List of IndexInfo with usage statistics
|
|
106
|
+
"""
|
|
107
|
+
async with self.pool.acquire() as conn:
|
|
108
|
+
# Query index information
|
|
109
|
+
query = """
|
|
110
|
+
SELECT
|
|
111
|
+
i.indexrelid::regclass AS index_name,
|
|
112
|
+
i.indrelid::regclass AS table_name,
|
|
113
|
+
array_agg(a.attname ORDER BY x.n) AS columns,
|
|
114
|
+
am.amname AS index_type,
|
|
115
|
+
i.indisunique AS is_unique,
|
|
116
|
+
pg_relation_size(i.indexrelid) AS size_bytes,
|
|
117
|
+
COALESCE(s.idx_scan, 0) AS usage_count
|
|
118
|
+
FROM
|
|
119
|
+
pg_index i
|
|
120
|
+
JOIN pg_class c ON c.oid = i.indrelid
|
|
121
|
+
JOIN pg_am am ON am.oid = c.relam
|
|
122
|
+
JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey)
|
|
123
|
+
CROSS JOIN LATERAL unnest(i.indkey) WITH ORDINALITY AS x(attnum, n)
|
|
124
|
+
LEFT JOIN pg_stat_user_indexes s ON s.indexrelid = i.indexrelid
|
|
125
|
+
WHERE
|
|
126
|
+
c.relname IN ('graph_entities', 'graph_relations')
|
|
127
|
+
AND a.attnum = x.attnum
|
|
128
|
+
GROUP BY
|
|
129
|
+
i.indexrelid, i.indrelid, am.amname, i.indisunique, s.idx_scan
|
|
130
|
+
ORDER BY
|
|
131
|
+
table_name, index_name
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
rows = await conn.fetch(query)
|
|
135
|
+
|
|
136
|
+
indexes = []
|
|
137
|
+
for row in rows:
|
|
138
|
+
indexes.append(
|
|
139
|
+
IndexInfo(
|
|
140
|
+
index_name=str(row["index_name"]),
|
|
141
|
+
table_name=str(row["table_name"]),
|
|
142
|
+
columns=list(row["columns"]),
|
|
143
|
+
index_type=row["index_type"],
|
|
144
|
+
is_unique=row["is_unique"],
|
|
145
|
+
size_bytes=row["size_bytes"],
|
|
146
|
+
usage_count=row["usage_count"],
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return indexes
|
|
151
|
+
|
|
152
|
+
async def get_unused_indexes(self, min_usage_threshold: int = 10) -> List[IndexInfo]:
|
|
153
|
+
"""
|
|
154
|
+
Find indexes that are rarely or never used
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
min_usage_threshold: Minimum usage count to consider index used
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
List of unused indexes
|
|
161
|
+
"""
|
|
162
|
+
indexes = await self.analyze_indexes()
|
|
163
|
+
unused = [idx for idx in indexes if idx.usage_count < min_usage_threshold]
|
|
164
|
+
return unused
|
|
165
|
+
|
|
166
|
+
async def get_missing_index_recommendations(
|
|
167
|
+
self,
|
|
168
|
+
) -> List[IndexRecommendation]:
|
|
169
|
+
"""
|
|
170
|
+
Analyze query patterns and recommend missing indexes
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
List of index recommendations
|
|
174
|
+
"""
|
|
175
|
+
recommendations = []
|
|
176
|
+
|
|
177
|
+
async with self.pool.acquire() as conn:
|
|
178
|
+
# Check for missing indexes based on common query patterns
|
|
179
|
+
|
|
180
|
+
# 1. Check if composite index on (entity_type, properties) would be beneficial
|
|
181
|
+
# This is useful for queries filtering by type and JSONB properties
|
|
182
|
+
entity_type_props_exists = await self._index_exists(
|
|
183
|
+
conn, "graph_entities", ["entity_type", "properties"]
|
|
184
|
+
)
|
|
185
|
+
if not entity_type_props_exists:
|
|
186
|
+
recommendations.append(
|
|
187
|
+
IndexRecommendation(
|
|
188
|
+
table_name="graph_entities",
|
|
189
|
+
columns=["entity_type", "properties"],
|
|
190
|
+
index_type="gin",
|
|
191
|
+
reason="Queries often filter by entity_type and search properties",
|
|
192
|
+
estimated_benefit="medium",
|
|
193
|
+
create_sql=(
|
|
194
|
+
"CREATE INDEX CONCURRENTLY idx_graph_entities_type_props "
|
|
195
|
+
"ON graph_entities (entity_type, properties jsonb_path_ops)"
|
|
196
|
+
),
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# 2. Check for relation composite indexes
|
|
201
|
+
source_type_exists = await self._index_exists(
|
|
202
|
+
conn, "graph_relations", ["source_id", "relation_type"]
|
|
203
|
+
)
|
|
204
|
+
if not source_type_exists:
|
|
205
|
+
recommendations.append(
|
|
206
|
+
IndexRecommendation(
|
|
207
|
+
table_name="graph_relations",
|
|
208
|
+
columns=["source_id", "relation_type"],
|
|
209
|
+
index_type="btree",
|
|
210
|
+
reason="Queries often filter relations by source and type",
|
|
211
|
+
estimated_benefit="high",
|
|
212
|
+
create_sql=(
|
|
213
|
+
"CREATE INDEX CONCURRENTLY idx_graph_relations_source_type "
|
|
214
|
+
"ON graph_relations (source_id, relation_type)"
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
target_type_exists = await self._index_exists(
|
|
220
|
+
conn, "graph_relations", ["target_id", "relation_type"]
|
|
221
|
+
)
|
|
222
|
+
if not target_type_exists:
|
|
223
|
+
recommendations.append(
|
|
224
|
+
IndexRecommendation(
|
|
225
|
+
table_name="graph_relations",
|
|
226
|
+
columns=["target_id", "relation_type"],
|
|
227
|
+
index_type="btree",
|
|
228
|
+
reason="Queries often filter incoming relations by target and type",
|
|
229
|
+
estimated_benefit="high",
|
|
230
|
+
create_sql=(
|
|
231
|
+
"CREATE INDEX CONCURRENTLY idx_graph_relations_target_type "
|
|
232
|
+
"ON graph_relations (target_id, relation_type)"
|
|
233
|
+
),
|
|
234
|
+
)
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# 3. Check for weight index (useful for weighted path finding)
|
|
238
|
+
weight_exists = await self._index_exists(conn, "graph_relations", ["weight"])
|
|
239
|
+
if not weight_exists:
|
|
240
|
+
recommendations.append(
|
|
241
|
+
IndexRecommendation(
|
|
242
|
+
table_name="graph_relations",
|
|
243
|
+
columns=["weight"],
|
|
244
|
+
index_type="btree",
|
|
245
|
+
reason="Weight-based path finding and sorting",
|
|
246
|
+
estimated_benefit="low",
|
|
247
|
+
create_sql=(
|
|
248
|
+
"CREATE INDEX CONCURRENTLY idx_graph_relations_weight "
|
|
249
|
+
"ON graph_relations (weight)"
|
|
250
|
+
),
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
# 4. Check for timestamp indexes (useful for temporal queries)
|
|
255
|
+
entity_created_exists = await self._index_exists(conn, "graph_entities", ["created_at"])
|
|
256
|
+
if not entity_created_exists:
|
|
257
|
+
recommendations.append(
|
|
258
|
+
IndexRecommendation(
|
|
259
|
+
table_name="graph_entities",
|
|
260
|
+
columns=["created_at"],
|
|
261
|
+
index_type="btree",
|
|
262
|
+
reason="Temporal queries (recently created entities)",
|
|
263
|
+
estimated_benefit="low",
|
|
264
|
+
create_sql=(
|
|
265
|
+
"CREATE INDEX CONCURRENTLY idx_graph_entities_created "
|
|
266
|
+
"ON graph_entities (created_at)"
|
|
267
|
+
),
|
|
268
|
+
)
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
return recommendations
|
|
272
|
+
|
|
273
|
+
async def _index_exists(
|
|
274
|
+
self, conn: asyncpg.Connection, table_name: str, columns: List[str]
|
|
275
|
+
) -> bool:
|
|
276
|
+
"""Check if an index exists on specified columns"""
|
|
277
|
+
query = """
|
|
278
|
+
SELECT EXISTS (
|
|
279
|
+
SELECT 1
|
|
280
|
+
FROM pg_index i
|
|
281
|
+
JOIN pg_class c ON c.oid = i.indrelid
|
|
282
|
+
JOIN pg_attribute a ON a.attrelid = i.indrelid
|
|
283
|
+
WHERE c.relname = $1
|
|
284
|
+
AND array_agg(a.attname) @> $2::text[]
|
|
285
|
+
GROUP BY i.indexrelid
|
|
286
|
+
)
|
|
287
|
+
"""
|
|
288
|
+
result = await conn.fetchval(query, table_name, columns)
|
|
289
|
+
return result or False
|
|
290
|
+
|
|
291
|
+
async def apply_recommendations(
|
|
292
|
+
self, recommendations: List[IndexRecommendation], dry_run: bool = False
|
|
293
|
+
) -> Dict[str, Any]:
|
|
294
|
+
"""
|
|
295
|
+
Apply index recommendations
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
recommendations: List of recommendations to apply
|
|
299
|
+
dry_run: If True, only show what would be done
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
Dictionary with results
|
|
303
|
+
"""
|
|
304
|
+
results: Dict[str, List[IndexRecommendation]] = {
|
|
305
|
+
"applied": [],
|
|
306
|
+
"failed": [],
|
|
307
|
+
"skipped": [],
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
for rec in recommendations:
|
|
311
|
+
try:
|
|
312
|
+
if dry_run:
|
|
313
|
+
logger.info(f"[DRY RUN] Would create index: {rec.create_sql}")
|
|
314
|
+
results["skipped"].append(
|
|
315
|
+
{"recommendation": rec.to_dict(), "reason": "dry_run"}
|
|
316
|
+
)
|
|
317
|
+
continue
|
|
318
|
+
|
|
319
|
+
# Create index
|
|
320
|
+
async with self.pool.acquire() as conn:
|
|
321
|
+
logger.info(f"Creating index: {rec.create_sql}")
|
|
322
|
+
await conn.execute(rec.create_sql)
|
|
323
|
+
results["applied"].append(rec.to_dict())
|
|
324
|
+
logger.info(
|
|
325
|
+
f"Successfully created index on {rec.table_name}({', '.join(rec.columns)})"
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
except Exception as e:
|
|
329
|
+
logger.error(f"Failed to create index: {e}")
|
|
330
|
+
results["failed"].append({"recommendation": rec.to_dict(), "error": str(e)})
|
|
331
|
+
|
|
332
|
+
return results
|
|
333
|
+
|
|
334
|
+
async def analyze_table_stats(self) -> Dict[str, Any]:
|
|
335
|
+
"""
|
|
336
|
+
Get table statistics for optimization decisions
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
Dictionary with table statistics
|
|
340
|
+
"""
|
|
341
|
+
async with self.pool.acquire() as conn:
|
|
342
|
+
stats_query = """
|
|
343
|
+
SELECT
|
|
344
|
+
schemaname,
|
|
345
|
+
tablename,
|
|
346
|
+
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS total_size,
|
|
347
|
+
pg_stat_get_live_tuples(schemaname||'.'||tablename::regclass) AS row_count,
|
|
348
|
+
pg_stat_get_dead_tuples(schemaname||'.'||tablename::regclass) AS dead_tuples,
|
|
349
|
+
last_vacuum,
|
|
350
|
+
last_autovacuum,
|
|
351
|
+
last_analyze,
|
|
352
|
+
last_autoanalyze
|
|
353
|
+
FROM pg_stat_user_tables
|
|
354
|
+
WHERE tablename IN ('graph_entities', 'graph_relations')
|
|
355
|
+
"""
|
|
356
|
+
|
|
357
|
+
rows = await conn.fetch(stats_query)
|
|
358
|
+
|
|
359
|
+
stats = {}
|
|
360
|
+
for row in rows:
|
|
361
|
+
stats[row["tablename"]] = {
|
|
362
|
+
"total_size": row["total_size"],
|
|
363
|
+
"row_count": row["row_count"],
|
|
364
|
+
"dead_tuples": row["dead_tuples"],
|
|
365
|
+
"last_vacuum": (row["last_vacuum"].isoformat() if row["last_vacuum"] else None),
|
|
366
|
+
"last_autovacuum": (
|
|
367
|
+
row["last_autovacuum"].isoformat() if row["last_autovacuum"] else None
|
|
368
|
+
),
|
|
369
|
+
"last_analyze": (
|
|
370
|
+
row["last_analyze"].isoformat() if row["last_analyze"] else None
|
|
371
|
+
),
|
|
372
|
+
"last_autoanalyze": (
|
|
373
|
+
row["last_autoanalyze"].isoformat() if row["last_autoanalyze"] else None
|
|
374
|
+
),
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return stats
|
|
378
|
+
|
|
379
|
+
async def vacuum_analyze(self, table_name: Optional[str] = None) -> None:
|
|
380
|
+
"""
|
|
381
|
+
Run VACUUM ANALYZE on graph tables
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
table_name: Specific table to analyze (None for all graph tables)
|
|
385
|
+
"""
|
|
386
|
+
tables = [table_name] if table_name else ["graph_entities", "graph_relations"]
|
|
387
|
+
|
|
388
|
+
async with self.pool.acquire() as conn:
|
|
389
|
+
for table in tables:
|
|
390
|
+
logger.info(f"Running VACUUM ANALYZE on {table}")
|
|
391
|
+
await conn.execute(f"VACUUM ANALYZE {table}")
|
|
392
|
+
logger.info(f"Completed VACUUM ANALYZE on {table}")
|
|
393
|
+
|
|
394
|
+
async def get_optimization_report(self) -> Dict[str, Any]:
|
|
395
|
+
"""
|
|
396
|
+
Generate comprehensive optimization report
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
Dictionary with optimization recommendations and statistics
|
|
400
|
+
"""
|
|
401
|
+
# Get all information
|
|
402
|
+
indexes = await self.analyze_indexes()
|
|
403
|
+
unused_indexes = await self.get_unused_indexes()
|
|
404
|
+
recommendations = await self.get_missing_index_recommendations()
|
|
405
|
+
table_stats = await self.analyze_table_stats()
|
|
406
|
+
|
|
407
|
+
# Calculate totals
|
|
408
|
+
total_index_size = sum(idx.size_bytes for idx in indexes)
|
|
409
|
+
unused_index_size = sum(idx.size_bytes for idx in unused_indexes)
|
|
410
|
+
|
|
411
|
+
report = {
|
|
412
|
+
"indexes": {
|
|
413
|
+
"total_count": len(indexes),
|
|
414
|
+
"total_size_mb": round(total_index_size / (1024 * 1024), 2),
|
|
415
|
+
"unused_count": len(unused_indexes),
|
|
416
|
+
"unused_size_mb": round(unused_index_size / (1024 * 1024), 2),
|
|
417
|
+
"details": [idx.to_dict() for idx in indexes],
|
|
418
|
+
},
|
|
419
|
+
"unused_indexes": [idx.to_dict() for idx in unused_indexes],
|
|
420
|
+
"recommendations": [rec.to_dict() for rec in recommendations],
|
|
421
|
+
"table_stats": table_stats,
|
|
422
|
+
"summary": {
|
|
423
|
+
"total_recommendations": len(recommendations),
|
|
424
|
+
"high_priority": len([r for r in recommendations if r.estimated_benefit == "high"]),
|
|
425
|
+
"medium_priority": len(
|
|
426
|
+
[r for r in recommendations if r.estimated_benefit == "medium"]
|
|
427
|
+
),
|
|
428
|
+
"low_priority": len([r for r in recommendations if r.estimated_benefit == "low"]),
|
|
429
|
+
"potential_space_savings_mb": round(unused_index_size / (1024 * 1024), 2),
|
|
430
|
+
},
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return report
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
# Pre-defined optimal index set for graph storage
|
|
437
|
+
OPTIMAL_INDEXES = [
|
|
438
|
+
{
|
|
439
|
+
"name": "idx_graph_entities_type",
|
|
440
|
+
"table": "graph_entities",
|
|
441
|
+
"sql": "CREATE INDEX IF NOT EXISTS idx_graph_entities_type ON graph_entities(entity_type)",
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
"name": "idx_graph_entities_properties",
|
|
445
|
+
"table": "graph_entities",
|
|
446
|
+
"sql": "CREATE INDEX IF NOT EXISTS idx_graph_entities_properties ON graph_entities USING GIN(properties)",
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
"name": "idx_graph_relations_type",
|
|
450
|
+
"table": "graph_relations",
|
|
451
|
+
"sql": "CREATE INDEX IF NOT EXISTS idx_graph_relations_type ON graph_relations(relation_type)",
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
"name": "idx_graph_relations_source",
|
|
455
|
+
"table": "graph_relations",
|
|
456
|
+
"sql": "CREATE INDEX IF NOT EXISTS idx_graph_relations_source ON graph_relations(source_id)",
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
"name": "idx_graph_relations_target",
|
|
460
|
+
"table": "graph_relations",
|
|
461
|
+
"sql": "CREATE INDEX IF NOT EXISTS idx_graph_relations_target ON graph_relations(target_id)",
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
"name": "idx_graph_relations_source_target",
|
|
465
|
+
"table": "graph_relations",
|
|
466
|
+
"sql": "CREATE INDEX IF NOT EXISTS idx_graph_relations_source_target ON graph_relations(source_id, target_id)",
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
"name": "idx_graph_relations_properties",
|
|
470
|
+
"table": "graph_relations",
|
|
471
|
+
"sql": "CREATE INDEX IF NOT EXISTS idx_graph_relations_properties ON graph_relations USING GIN(properties)",
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
"name": "idx_graph_relations_source_type",
|
|
475
|
+
"table": "graph_relations",
|
|
476
|
+
"sql": "CREATE INDEX IF NOT EXISTS idx_graph_relations_source_type ON graph_relations(source_id, relation_type)",
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
"name": "idx_graph_relations_target_type",
|
|
480
|
+
"table": "graph_relations",
|
|
481
|
+
"sql": "CREATE INDEX IF NOT EXISTS idx_graph_relations_target_type ON graph_relations(target_id, relation_type)",
|
|
482
|
+
},
|
|
483
|
+
]
|