mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	DB Optimizations (#6712)
* Enable auto vacuums * Enable auto vacuum * Fix separator * Fix separator and remove incorrect log * Limit to 1 row since that is all that is used * Add index on camera + segment_size * Formatting * Increase timeout and cache_size * Set DB mode to NORMAL synchronous level * Formatting * Vacuum every 2 weeks * Remove fstring * Use string * Use consts
This commit is contained in:
		
							parent
							
								
									20b52a96bc
								
							
						
					
					
						commit
						8bc76d19db
					
				| @ -1,3 +1,4 @@ | ||||
| import datetime | ||||
| import logging | ||||
| import multiprocessing as mp | ||||
| import os | ||||
| @ -167,6 +168,15 @@ class FrigateApp: | ||||
|         self.timeline_queue: Queue = mp.Queue() | ||||
| 
 | ||||
|     def init_database(self) -> None: | ||||
|         def vacuum_db(db: SqliteExtDatabase) -> None: | ||||
|             db.execute_sql("VACUUM;") | ||||
| 
 | ||||
|             try: | ||||
|                 with open(f"{CONFIG_DIR}/.vacuum", "w") as f: | ||||
|                     f.write(str(datetime.datetime.now().timestamp())) | ||||
|             except PermissionError: | ||||
|                 logger.error("Unable to write to /config to save DB state") | ||||
| 
 | ||||
|         # Migrate DB location | ||||
|         old_db_path = DEFAULT_DB_PATH | ||||
|         if not os.path.isfile(self.config.database.path) and os.path.isfile( | ||||
| @ -182,6 +192,24 @@ class FrigateApp: | ||||
|         router = Router(migrate_db) | ||||
|         router.run() | ||||
| 
 | ||||
|         # check if vacuum needs to be run | ||||
|         if os.path.exists(f"{CONFIG_DIR}/.vacuum"): | ||||
|             with open(f"{CONFIG_DIR}/.vacuum") as f: | ||||
|                 try: | ||||
|                     timestamp = int(f.readline()) | ||||
|                 except Exception: | ||||
|                     timestamp = 0 | ||||
| 
 | ||||
|                 if ( | ||||
|                     timestamp | ||||
|                     < ( | ||||
|                         datetime.datetime.now() - datetime.timedelta(weeks=2) | ||||
|                     ).timestamp() | ||||
|                 ): | ||||
|                     vacuum_db(migrate_db) | ||||
|         else: | ||||
|             vacuum_db(migrate_db) | ||||
| 
 | ||||
|         migrate_db.close() | ||||
| 
 | ||||
|     def init_go2rtc(self) -> None: | ||||
| @ -205,7 +233,15 @@ class FrigateApp: | ||||
|     def bind_database(self) -> None: | ||||
|         """Bind db to the main process.""" | ||||
|         # NOTE: all db accessing processes need to be created before the db can be bound to the main process | ||||
|         self.db = SqliteQueueDatabase(self.config.database.path) | ||||
|         self.db = SqliteQueueDatabase( | ||||
|             self.config.database.path, | ||||
|             pragmas={ | ||||
|                 "auto_vacuum": "FULL",  # Does not defragment database | ||||
|                 "cache_size": -512 * 1000,  # 512MB of cache, | ||||
|                 "synchronous": "NORMAL",  # Safe when using WAL https://www.sqlite.org/pragma.html#pragma_synchronous | ||||
|             }, | ||||
|             timeout=60, | ||||
|         ) | ||||
|         models = [Event, Recordings, Timeline] | ||||
|         self.db.bind(models) | ||||
| 
 | ||||
|  | ||||
| @ -180,7 +180,9 @@ class RecordingCleanup(threading.Thread): | ||||
| 
 | ||||
|         # find all the recordings older than the oldest recording in the db | ||||
|         try: | ||||
|             oldest_recording = Recordings.select().order_by(Recordings.start_time).get() | ||||
|             oldest_recording = ( | ||||
|                 Recordings.select().order_by(Recordings.start_time).limit(1).get() | ||||
|             ) | ||||
| 
 | ||||
|             p = Path(oldest_recording.path) | ||||
|             oldest_timestamp = p.stat().st_mtime - 1 | ||||
|  | ||||
| @ -37,7 +37,15 @@ def manage_recordings( | ||||
|     setproctitle("frigate.recording_manager") | ||||
|     listen() | ||||
| 
 | ||||
|     db = SqliteQueueDatabase(config.database.path) | ||||
|     db = SqliteQueueDatabase( | ||||
|         config.database.path, | ||||
|         pragmas={ | ||||
|             "auto_vacuum": "FULL",  # Does not defragment database | ||||
|             "cache_size": -512 * 1000,  # 512MB of cache | ||||
|             "synchronous": "NORMAL",  # Safe when using WAL https://www.sqlite.org/pragma.html#pragma_synchronous | ||||
|         }, | ||||
|         timeout=60, | ||||
|     ) | ||||
|     models = [Event, Recordings, Timeline] | ||||
|     db.bind(models) | ||||
| 
 | ||||
| @ -48,5 +56,3 @@ def manage_recordings( | ||||
| 
 | ||||
|     cleanup = RecordingCleanup(config, stop_event) | ||||
|     cleanup.start() | ||||
| 
 | ||||
|     logger.info("recording_manager: exiting subprocess") | ||||
|  | ||||
| @ -36,9 +36,7 @@ class StorageMaintainer(threading.Thread): | ||||
|                 self.camera_storage_stats[camera] = { | ||||
|                     "needs_refresh": ( | ||||
|                         Recordings.select(fn.COUNT(Recordings.id)) | ||||
|                         .where( | ||||
|                             Recordings.camera == camera, Recordings.segment_size != 0 | ||||
|                         ) | ||||
|                         .where(Recordings.camera == camera, Recordings.segment_size > 0) | ||||
|                         .scalar() | ||||
|                         < 50 | ||||
|                     ) | ||||
| @ -48,7 +46,7 @@ class StorageMaintainer(threading.Thread): | ||||
|             try: | ||||
|                 bandwidth = round( | ||||
|                     Recordings.select(fn.AVG(bandwidth_equation)) | ||||
|                     .where(Recordings.camera == camera, Recordings.segment_size != 0) | ||||
|                     .where(Recordings.camera == camera, Recordings.segment_size > 0) | ||||
|                     .limit(100) | ||||
|                     .scalar() | ||||
|                     * 3600, | ||||
| @ -178,6 +176,7 @@ class StorageMaintainer(threading.Thread): | ||||
| 
 | ||||
|     def run(self): | ||||
|         """Check every 5 minutes if storage needs to be cleaned up.""" | ||||
|         self.calculate_camera_bandwidth() | ||||
|         while not self.stop_event.wait(300): | ||||
|             if not self.camera_storage_stats or True in [ | ||||
|                 r["needs_refresh"] for r in self.camera_storage_stats.values() | ||||
|  | ||||
							
								
								
									
										35
									
								
								migrations/017_update_indexes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								migrations/017_update_indexes.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| """Peewee migrations -- 017_update_indexes.py. | ||||
| 
 | ||||
| Some examples (model - class or model name):: | ||||
| 
 | ||||
|     > Model = migrator.orm['model_name']            # Return model in current state by name | ||||
| 
 | ||||
|     > migrator.sql(sql)                             # Run custom SQL | ||||
|     > migrator.python(func, *args, **kwargs)        # Run python code | ||||
|     > migrator.create_model(Model)                  # Create a model (could be used as decorator) | ||||
|     > migrator.remove_model(model, cascade=True)    # Remove a model | ||||
|     > migrator.add_fields(model, **fields)          # Add fields to a model | ||||
|     > migrator.change_fields(model, **fields)       # Change fields | ||||
|     > migrator.remove_fields(model, *field_names, cascade=True) | ||||
|     > migrator.rename_field(model, old_field_name, new_field_name) | ||||
|     > migrator.rename_table(model, new_table_name) | ||||
|     > migrator.add_index(model, *col_names, unique=False) | ||||
|     > migrator.drop_index(model, *col_names) | ||||
|     > migrator.add_not_null(model, *field_names) | ||||
|     > migrator.drop_not_null(model, *field_names) | ||||
|     > migrator.add_default(model, field_name, default) | ||||
| 
 | ||||
| """ | ||||
| import peewee as pw | ||||
| 
 | ||||
| SQL = pw.SQL | ||||
| 
 | ||||
| 
 | ||||
| def migrate(migrator, database, fake=False, **kwargs): | ||||
|     migrator.sql( | ||||
|         'CREATE INDEX "recordings_camera_segment_size" ON "recordings" ("camera", "segment_size")' | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def rollback(migrator, database, fake=False, **kwargs): | ||||
|     pass | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user