sqlmesh.core.janitor
1from __future__ import annotations 2 3import typing as t 4 5from sqlglot import exp 6 7from sqlmesh.core.engine_adapter import EngineAdapter 8from sqlmesh.core.console import Console 9from sqlmesh.core.dialect import schema_ 10from sqlmesh.core.environment import Environment 11from sqlmesh.core.snapshot import SnapshotEvaluator 12from sqlmesh.core.state_sync import StateSync 13from sqlmesh.core.state_sync.common import ( 14 logger, 15 iter_expired_snapshot_batches, 16 RowBoundary, 17 ExpiredBatchRange, 18) 19 20 21def cleanup_expired_views( 22 default_adapter: EngineAdapter, 23 engine_adapters: t.Dict[str, EngineAdapter], 24 environments: t.List[Environment], 25 console: t.Optional[Console] = None, 26) -> t.List[str]: 27 failures: t.List[str] = [] 28 29 expired_schema_or_catalog_environments = [ 30 environment 31 for environment in environments 32 if environment.suffix_target.is_schema or environment.suffix_target.is_catalog 33 ] 34 expired_table_environments = [ 35 environment for environment in environments if environment.suffix_target.is_table 36 ] 37 38 # We have to use the corresponding adapter if the virtual layer is gateway managed 39 def get_adapter(gateway_managed: bool, gateway: t.Optional[str] = None) -> EngineAdapter: 40 if gateway_managed and gateway: 41 return engine_adapters.get(gateway, default_adapter) 42 return default_adapter 43 44 catalogs_to_drop: t.Set[t.Tuple[EngineAdapter, str]] = set() 45 schemas_to_drop: t.Set[t.Tuple[EngineAdapter, exp.Table]] = set() 46 47 # Collect schemas and catalogs to drop 48 for engine_adapter, expired_catalog, expired_schema, suffix_target in { 49 ( 50 (engine_adapter := get_adapter(environment.gateway_managed, snapshot.model_gateway)), 51 snapshot.qualified_view_name.catalog_for_environment( 52 environment.naming_info, dialect=engine_adapter.dialect 53 ), 54 snapshot.qualified_view_name.schema_for_environment( 55 environment.naming_info, dialect=engine_adapter.dialect 56 ), 57 environment.suffix_target, 58 ) 59 for environment in expired_schema_or_catalog_environments 60 for snapshot in environment.snapshots 61 if snapshot.is_model and not snapshot.is_symbolic 62 }: 63 if suffix_target.is_catalog: 64 if expired_catalog: 65 catalogs_to_drop.add((engine_adapter, expired_catalog)) 66 else: 67 schema = schema_(expired_schema, expired_catalog) 68 schemas_to_drop.add((engine_adapter, schema)) 69 70 # Drop the views for the expired environments 71 for engine_adapter, expired_view in { 72 ( 73 (engine_adapter := get_adapter(environment.gateway_managed, snapshot.model_gateway)), 74 snapshot.qualified_view_name.for_environment( 75 environment.naming_info, dialect=engine_adapter.dialect 76 ), 77 ) 78 for environment in expired_table_environments 79 for snapshot in environment.snapshots 80 if snapshot.is_model and not snapshot.is_symbolic 81 }: 82 try: 83 engine_adapter.drop_view(expired_view, ignore_if_not_exists=True) 84 if console: 85 console.update_cleanup_progress(expired_view) 86 except Exception as e: 87 message = f"Failed to drop the expired environment view '{expired_view}': {e}" 88 logger.warning(message) 89 failures.append(message) 90 91 # Drop the schemas for the expired environments 92 for engine_adapter, schema in schemas_to_drop: 93 try: 94 engine_adapter.drop_schema( 95 schema, 96 ignore_if_not_exists=True, 97 cascade=True, 98 ) 99 if console: 100 console.update_cleanup_progress(schema.sql(dialect=engine_adapter.dialect)) 101 except Exception as e: 102 message = f"Failed to drop the expired environment schema '{schema}': {e}" 103 logger.warning(message) 104 failures.append(message) 105 106 # Drop any catalogs that were associated with a snapshot where the engine adapter supports dropping catalogs 107 # catalogs_to_drop is only populated when environment_suffix_target is set to 'catalog' 108 for engine_adapter, catalog in catalogs_to_drop: 109 if engine_adapter.SUPPORTS_CREATE_DROP_CATALOG: 110 try: 111 engine_adapter.drop_catalog(catalog) 112 if console: 113 console.update_cleanup_progress(catalog) 114 except Exception as e: 115 message = f"Failed to drop the expired environment catalog '{catalog}': {e}" 116 logger.warning(message) 117 failures.append(message) 118 119 return failures 120 121 122def delete_expired_snapshots( 123 state_sync: StateSync, 124 snapshot_evaluator: SnapshotEvaluator, 125 *, 126 current_ts: int, 127 ignore_ttl: bool = False, 128 force_delete: bool = False, 129 batch_size: t.Optional[int] = None, 130 console: t.Optional[Console] = None, 131) -> t.List[str]: 132 """Delete all expired snapshots in batches. 133 134 This helper function encapsulates the logic for deleting expired snapshots in batches, 135 eliminating code duplication across different use cases. 136 137 Args: 138 state_sync: StateSync instance to query and delete expired snapshots from. 139 snapshot_evaluator: SnapshotEvaluator instance to clean up tables associated with snapshots. 140 current_ts: Timestamp used to evaluate expiration. 141 ignore_ttl: If True, include snapshots regardless of TTL (only checks if unreferenced). 142 force_delete: If True, delete snapshot state records even when physical table cleanup fails. 143 batch_size: Maximum number of snapshots to fetch per batch. 144 console: Optional console for reporting progress. 145 146 Returns: 147 List of failure messages so callers can surface them at the end of the janitor run. 148 """ 149 failures: t.List[str] = [] 150 num_expired_snapshots = 0 151 for batch in iter_expired_snapshot_batches( 152 state_reader=state_sync, 153 current_ts=current_ts, 154 ignore_ttl=ignore_ttl, 155 batch_size=batch_size, 156 ): 157 end_info = ( 158 f"updated_ts={batch.batch_range.end.updated_ts}" 159 if isinstance(batch.batch_range.end, RowBoundary) 160 else f"limit={batch.batch_range.end.batch_size}" 161 ) 162 logger.info( 163 "Processing batch of size %s with end %s", 164 len(batch.expired_snapshot_ids), 165 end_info, 166 ) 167 cleanup_succeeded = True 168 try: 169 snapshot_evaluator.cleanup( 170 target_snapshots=batch.cleanup_tasks, 171 on_complete=console.update_cleanup_progress if console else None, 172 ) 173 except Exception as failed_drops: 174 message = f"Failed to clean up: {failed_drops}" 175 logger.warning(message) 176 failures.append(message) 177 cleanup_succeeded = False 178 179 if cleanup_succeeded or force_delete: 180 try: 181 state_sync.delete_expired_snapshots( 182 batch_range=ExpiredBatchRange( 183 start=RowBoundary.lowest_boundary(), 184 end=batch.batch_range.end, 185 ), 186 ignore_ttl=ignore_ttl, 187 ) 188 logger.info("Cleaned up expired snapshots batch") 189 num_expired_snapshots += len(batch.expired_snapshot_ids) 190 except Exception as e: 191 message = f"Failed to delete expired snapshot state records: {e}" 192 logger.warning(message) 193 failures.append(message) 194 logger.info("Cleaned up %s expired snapshots", num_expired_snapshots) 195 return failures
def
cleanup_expired_views( default_adapter: sqlmesh.core.engine_adapter.base.EngineAdapter, engine_adapters: Dict[str, sqlmesh.core.engine_adapter.base.EngineAdapter], environments: List[sqlmesh.core.environment.Environment], console: Optional[sqlmesh.core.console.Console] = None) -> List[str]:
22def cleanup_expired_views( 23 default_adapter: EngineAdapter, 24 engine_adapters: t.Dict[str, EngineAdapter], 25 environments: t.List[Environment], 26 console: t.Optional[Console] = None, 27) -> t.List[str]: 28 failures: t.List[str] = [] 29 30 expired_schema_or_catalog_environments = [ 31 environment 32 for environment in environments 33 if environment.suffix_target.is_schema or environment.suffix_target.is_catalog 34 ] 35 expired_table_environments = [ 36 environment for environment in environments if environment.suffix_target.is_table 37 ] 38 39 # We have to use the corresponding adapter if the virtual layer is gateway managed 40 def get_adapter(gateway_managed: bool, gateway: t.Optional[str] = None) -> EngineAdapter: 41 if gateway_managed and gateway: 42 return engine_adapters.get(gateway, default_adapter) 43 return default_adapter 44 45 catalogs_to_drop: t.Set[t.Tuple[EngineAdapter, str]] = set() 46 schemas_to_drop: t.Set[t.Tuple[EngineAdapter, exp.Table]] = set() 47 48 # Collect schemas and catalogs to drop 49 for engine_adapter, expired_catalog, expired_schema, suffix_target in { 50 ( 51 (engine_adapter := get_adapter(environment.gateway_managed, snapshot.model_gateway)), 52 snapshot.qualified_view_name.catalog_for_environment( 53 environment.naming_info, dialect=engine_adapter.dialect 54 ), 55 snapshot.qualified_view_name.schema_for_environment( 56 environment.naming_info, dialect=engine_adapter.dialect 57 ), 58 environment.suffix_target, 59 ) 60 for environment in expired_schema_or_catalog_environments 61 for snapshot in environment.snapshots 62 if snapshot.is_model and not snapshot.is_symbolic 63 }: 64 if suffix_target.is_catalog: 65 if expired_catalog: 66 catalogs_to_drop.add((engine_adapter, expired_catalog)) 67 else: 68 schema = schema_(expired_schema, expired_catalog) 69 schemas_to_drop.add((engine_adapter, schema)) 70 71 # Drop the views for the expired environments 72 for engine_adapter, expired_view in { 73 ( 74 (engine_adapter := get_adapter(environment.gateway_managed, snapshot.model_gateway)), 75 snapshot.qualified_view_name.for_environment( 76 environment.naming_info, dialect=engine_adapter.dialect 77 ), 78 ) 79 for environment in expired_table_environments 80 for snapshot in environment.snapshots 81 if snapshot.is_model and not snapshot.is_symbolic 82 }: 83 try: 84 engine_adapter.drop_view(expired_view, ignore_if_not_exists=True) 85 if console: 86 console.update_cleanup_progress(expired_view) 87 except Exception as e: 88 message = f"Failed to drop the expired environment view '{expired_view}': {e}" 89 logger.warning(message) 90 failures.append(message) 91 92 # Drop the schemas for the expired environments 93 for engine_adapter, schema in schemas_to_drop: 94 try: 95 engine_adapter.drop_schema( 96 schema, 97 ignore_if_not_exists=True, 98 cascade=True, 99 ) 100 if console: 101 console.update_cleanup_progress(schema.sql(dialect=engine_adapter.dialect)) 102 except Exception as e: 103 message = f"Failed to drop the expired environment schema '{schema}': {e}" 104 logger.warning(message) 105 failures.append(message) 106 107 # Drop any catalogs that were associated with a snapshot where the engine adapter supports dropping catalogs 108 # catalogs_to_drop is only populated when environment_suffix_target is set to 'catalog' 109 for engine_adapter, catalog in catalogs_to_drop: 110 if engine_adapter.SUPPORTS_CREATE_DROP_CATALOG: 111 try: 112 engine_adapter.drop_catalog(catalog) 113 if console: 114 console.update_cleanup_progress(catalog) 115 except Exception as e: 116 message = f"Failed to drop the expired environment catalog '{catalog}': {e}" 117 logger.warning(message) 118 failures.append(message) 119 120 return failures
def
delete_expired_snapshots( state_sync: sqlmesh.core.state_sync.base.StateSync, snapshot_evaluator: sqlmesh.core.snapshot.evaluator.SnapshotEvaluator, *, current_ts: int, ignore_ttl: bool = False, force_delete: bool = False, batch_size: Optional[int] = None, console: Optional[sqlmesh.core.console.Console] = None) -> List[str]:
123def delete_expired_snapshots( 124 state_sync: StateSync, 125 snapshot_evaluator: SnapshotEvaluator, 126 *, 127 current_ts: int, 128 ignore_ttl: bool = False, 129 force_delete: bool = False, 130 batch_size: t.Optional[int] = None, 131 console: t.Optional[Console] = None, 132) -> t.List[str]: 133 """Delete all expired snapshots in batches. 134 135 This helper function encapsulates the logic for deleting expired snapshots in batches, 136 eliminating code duplication across different use cases. 137 138 Args: 139 state_sync: StateSync instance to query and delete expired snapshots from. 140 snapshot_evaluator: SnapshotEvaluator instance to clean up tables associated with snapshots. 141 current_ts: Timestamp used to evaluate expiration. 142 ignore_ttl: If True, include snapshots regardless of TTL (only checks if unreferenced). 143 force_delete: If True, delete snapshot state records even when physical table cleanup fails. 144 batch_size: Maximum number of snapshots to fetch per batch. 145 console: Optional console for reporting progress. 146 147 Returns: 148 List of failure messages so callers can surface them at the end of the janitor run. 149 """ 150 failures: t.List[str] = [] 151 num_expired_snapshots = 0 152 for batch in iter_expired_snapshot_batches( 153 state_reader=state_sync, 154 current_ts=current_ts, 155 ignore_ttl=ignore_ttl, 156 batch_size=batch_size, 157 ): 158 end_info = ( 159 f"updated_ts={batch.batch_range.end.updated_ts}" 160 if isinstance(batch.batch_range.end, RowBoundary) 161 else f"limit={batch.batch_range.end.batch_size}" 162 ) 163 logger.info( 164 "Processing batch of size %s with end %s", 165 len(batch.expired_snapshot_ids), 166 end_info, 167 ) 168 cleanup_succeeded = True 169 try: 170 snapshot_evaluator.cleanup( 171 target_snapshots=batch.cleanup_tasks, 172 on_complete=console.update_cleanup_progress if console else None, 173 ) 174 except Exception as failed_drops: 175 message = f"Failed to clean up: {failed_drops}" 176 logger.warning(message) 177 failures.append(message) 178 cleanup_succeeded = False 179 180 if cleanup_succeeded or force_delete: 181 try: 182 state_sync.delete_expired_snapshots( 183 batch_range=ExpiredBatchRange( 184 start=RowBoundary.lowest_boundary(), 185 end=batch.batch_range.end, 186 ), 187 ignore_ttl=ignore_ttl, 188 ) 189 logger.info("Cleaned up expired snapshots batch") 190 num_expired_snapshots += len(batch.expired_snapshot_ids) 191 except Exception as e: 192 message = f"Failed to delete expired snapshot state records: {e}" 193 logger.warning(message) 194 failures.append(message) 195 logger.info("Cleaned up %s expired snapshots", num_expired_snapshots) 196 return failures
Delete all expired snapshots in batches.
This helper function encapsulates the logic for deleting expired snapshots in batches, eliminating code duplication across different use cases.
Arguments:
- state_sync: StateSync instance to query and delete expired snapshots from.
- snapshot_evaluator: SnapshotEvaluator instance to clean up tables associated with snapshots.
- current_ts: Timestamp used to evaluate expiration.
- ignore_ttl: If True, include snapshots regardless of TTL (only checks if unreferenced).
- force_delete: If True, delete snapshot state records even when physical table cleanup fails.
- batch_size: Maximum number of snapshots to fetch per batch.
- console: Optional console for reporting progress.
Returns:
List of failure messages so callers can surface them at the end of the janitor run.