Earlier this week, I blogged about the automatic cleanup process that purges old data from the SSIS catalog logging tables. This nightly process removes data for operations that are older than 365 days. While this is useful, many SSIS admins have complained that this process is very slow and contentious on large or busy SSISDB databases.
In this post, I’ll show to you one of the main reasons this purge process is slow, and will share a more efficient way of performing this delete operation.
Investigating the delete operation
When the catalog purge takes place, it removes the old operational and execution data from most of the log tables, including:
… and many others. Some of these tables can grow quite large, but since the process runs each night, the percent of data being deleted is relatively small. So why does it take so long to run?
To investigate more deeply, we’ll open up the stored procedure [internal].[cleanup_server_retention_window], which is invoked each night via SQL Server Agent job. When looking through this stored procedure, one would expect to see a score of score operations to touch each one of these tables. However, what you’d find is substantially simpler. Below is an example of the centerpiece of the delete operation in the purge stored procedure.
Rather than twenty or more DELETE statements, we find just one, which deletes from the [internal].[operations] table.
As it turns out, deleting from the [internal].[operations] table will handle all of the delete operations, for one reason: All of the foreign keys are built using the setting ON DELETE CASCADE. When using this setting in a foreign key, you can delete a row in a table referenced by a foreign key, and the delete operation will also remove any rows in the table that references that particular row. In theory, this is a good idea: it helps to ensure referential integrity, and reduces the amount of code you have to write to delete data. However, using the cascading deletes can lead to lots of blocking, particularly when those deletes are chained together across multiple tables.
One example of this chaining is the [internal].[execution_parameter_values] table. To delete from this table, the SSIS purge process deletes the [internal].[operations] table. This table is referenced by a foreign key on the [internal].[executions] table, so the update transaction is now deleting from two tables by way of the ON DELETE CASCADE foreign key. Finally, the [internal].[execution_parameter_values] table has a foreign key referencing [internal].[executions], which means that we now have three different tables involved in a delete of a single row on the parent table. While this may not present an issue with small and less busy SSISDB databases, it is not uncommon to encounter contention issues with the purge process for very busy SSIS catalog databases.
A more direct approach
While I am a fan of writing less code when possible, I’ve learned that relying on the ON DELETE CASCADE constraint of the foreign key is not the most efficient way to handle purge operations from the SSIS catalog. To that end, I’ve created my own deletion script that touches each table individually to purge old data. This script creates a working list of operation IDs in a temp table, which will be used as a reference to identify the rows to be deleted in each log table.
Using that list of IDs, this script will then process the essential logging tables in reverse order of granularity (smallest to largest) as defined by the foreign keys on those tables. The order of these delete operations is important to ensure an organic delete rather than relying on the ON DELETE CASCADE setting of the foreign keys.
The script below is a sample of what I use to perform the cleanup operation on the SSISDB catalog database. As you can see, it handles each table individually, using a list of execution_id values built by querying a list of old executions based on the retention settings in the SSIS catalog. As you’ll see, a few of the tables have special handling to delete data in batches of 1 million rows, due to the amount of data that can accumulate in those tables.
How to use this script
To use this script, I recommend that you create a separate user-defined stored procedure in the SSISDB database. Although you could simply replace the code in the internal.cleanup_server_retention_window stored procedure, it is a best practice to avoid modifying any of the existing DDL in the SSISDB database. It is much safer to create your own stored procedure for this, preferably in a new schema that you also create to build some logical and functional separation from the built-in SSIS catalog objects. You can then create your own SQL Server Agent job that executes this stored procedure.
While the built-in automated purge process in the SSISDB is fine to use for average workloads, one can encounter performance and contention issues on large and/or busy SSISDB databases. Using a more direct, table-by-table approach requires more code, but will perform better and provide more flexibility.