Introduction
Data compression is a feature that was added in SQL Server
2008. It enabled compression on tables
and indexes over either rows or pages of data.
Although it works reasonably well, there a number of limitations around
compression of inserted or updated data.
Since its introduction, there have been articles published
praising the value of using compression in a Dynamics 2012 environment. After some testing, it was found that
compression really does not work out of the box and when it does, it is very
clumsy to use. The discussion below
outlines some of those limitations and how they can be worked around.
Environment
Our in house environment currently consists of Dynamics Ax
2012 R2 CU6 (we are in the process of upgrading to CU9), SQL Server 2012 and
Windows Server 2012. Storage is maintained on a NAS Server separated into
different partitions for different purposes.
Initial Problem
A large portion of our testing environment is housed on a
partition of the NAS and may have up to 4 copy backs of production data for
different purposes. Although on the
small side, our current Ax database is about 200 GB. Given projections for growth and immediate
space storage limitations, it was decided to investigate whether compression
would be a viable short term alternative to save space. Consideration would also need to be given to
performance impacts.
b
However, on our local installation, the form was somewhat
less expansive.
This change was not particularly helpful. So what happened? It turns out the determination of whether
compression is supported is made in a class called SysSqlSetupHelper.
You may check if you want, but suffice it to say that
version 10 is SQL 2008. I suspect that
someone did not get the memo saying that SQL 2008 was not the terminal
release. Fixing this issue is relatively
minor. If we substitute the string
version with a real and determine that it is greater than or equal to 10.0, we
should be fine.
And when the form is run again, the compression options do
appear.
Compression Complexity
If you paid attention to the form SysSqlSetup, you would
notice that there are 5 option for compression.
1 Table, 1 Index on the Selected Table, All Indexes for Selected Table,
All Tables and All Indexes. Doing a quick
check in SQL Server would reveal that there are about 5,000 Tables and 12,000
Indexes in the Dynamics Ax 2012 database.
However the compression recommendations coming out of Microsoft indicate
that some tables/indexes should be left alone, some should have row compression
and others page compression. (https://msdn.microsoft.com/en-us/library/dd894051(SQL.100).aspx).
The decision of how and what to compress is based on an
analysis of the usage patterns (percentage of update operations and scan
operations) of each table an index. This
scan reveals only candidates for compression.
Then a SQL stored procedure (sp_estimate_data_compression_savings) can
give an indication of whether row or page compression will yield greater
results.
During the course of research, the article, “Demonstrating the Effects of Using Data
Compression in SQL Server” (https://www.mssqltips.com/sqlservertip/3187/demonstrating-the-effects-of-using-data-compression-in-sql-server/), outlined the performance impacts
of using SQL. Under the controlled
circumstances indicated, it was found that compression actually improves query
performance, but that the cost of slightly increasing CPU utilization
rates. At the bottom of the article,
there is comment from Cees Meuleman dated October 19, 2015. In the comment he provides a link to a script
that uses the principles from the 2 articles above. I have revised the stored procedure slightly,
but kept the original principals. A revised
copy has been attached in the Appendix.
The default operation of this stored procedure only
evaluates tables and indexes that extend over 50 pages (400 KB). For these objects, if the scan percentage is
greater than 75% and the update percentage is less than 20% and the savings
from page compression less than the savings from row compression is greater
than 20 percentage points, then a page compression is indicated. If both the scan and update percentage are
both lower than 5%, then no compression is indicated. If neither category is met, then row
compression is indicated. The routine
then has an optional section to perform the compression. Then an amendment to the original merges the
data into a new table placed in the master database.
New Problems
The actual mechanism to compress a table or an index is an
alter statement:
ALTER TABLE dbo.myTable REBUILD PARTITION = ALL WITH
(DATA_COMPRESSION = ‘PAGE|ROW|NONE’, ONLINE=ON|OFF, SORT_IN_TEMPDB=ON|OFF)
ALTER INDEX dbo.myIndex REBUILD PARTITION = ALL WITH
(DATA_COMPRESSION = ‘PAGE|ROW|NONE’, ONLINE=ON|OFF, SORT_IN_TEMPDB=ON|OFF)
The first problem that should be noted is that there may be
a problem with SORT_IN_TEMPDB. If the
collation of the Ax does not match the collation of TempDB, then sorting in
TempDB will fail. It should be noted
that the default collation of SQL Server (SQL_Latin1_General_CP1_CI_AS) does
not match the default collation of Dynamics Ax (Latin1_General_CI_AS). If the collations are different, then
SORT_IN_TEMPDB should be excluded or set to off. The collation can be checked by selected the
database in SSMS, right clicking and selecting Properties -> Options.
While running the store procedure above will compress
designated objects in the database, the compression can be removed if the
indexes or tables are rebuilt without the compression predicates used in the
alter statements. This will occur
whenever a database synchronize database is made from an AOT or if automated
tools are used to update statistics/reorganize/rebuild indexes.
Batch Job to Populate SQLSTORAGE
This is where the hacking element comes into play. In order to assure that the objects
compresses survive an AOT database synchronization the table that feeds the
compression options must be filled correctly.
Also, our index analysis tools are based in the master database and it
will be modified to use the data compression information supplied by the stored
procedure. If you experiment with the form SysSQLSetup and apply compression
definitions to a table and couple of indexes, you will get something like the
following in the table SQLSTORAGE (NB: access to this table is only from
SSMS. This table cannot be opened from
the AOT).
Tables:
Indexes:
Each object has 2 sets of records. The first sets out each parameter along with
its value (ID = 0) and the second provides the full predicate to use in the
index rebuild (ID = 1). In order to
generate the correct values, a stored procedure was created
GetCompressionParametersAx. This stored
procedure is then called through a batch process to process the result records
and merge them into SQL Storage. The
complication here is that some of the table and index ids had to be found in
the main Ax database, others had to be found in the Ax Model database. For compression values, 0 = NONE, 1 = PAGE
and 2 = ROW.
This stored procedure is then called by a batch service
controlled by PC_SQLCompressionController.
The details are contained within the attached project xpo. This also includes the full batch job, along
with the changes to SysSQLSetupHelper and the 2 forms SysSqlAdmin and
SysSqlSetup (unchanged) for reference.
The service itself is nothing special.
It simply calls the stored procedure, finds the matching table buffer in
SQLStorage and merges the results. This
prepares the correct data so that the synchronization will succeed.
Modifications to Index Evaluation Jobs
In our shop, we use the SQL Server Maintenance Solution
published by Ola Hallengren (https://ola.hallengren.com/). We have been using the index optimize on user
databases for over a year on a daily basis without any issues and considerable
performance enhancements. The table
IndexCompression was added to the master database as part of the first stored
procedure above. The data in the table
is now consumed. All that is required
here is to add the compression predicate to statement that that the routine is
building.
This revised job has been running on my development machine
for about a week and without any side effects.
Results
All of this effort would not have been worthwhile if there
were no noticeable results. Calling the
Shrink File Form in SSMS before compression shows a data file that is complete
full.
After compression, 48% of the main data file has been
compressed. Giving a net improvement
here of 80 GB.
Another experiment in the test environment results in a
similar improvement of about 50% (or about 100 GB).
Future Steps
Our current configuration is still placed on a single
partition. At some point in the future,
archived data should be moved to a separate set of partitions. This would require some tuning of the main
procedure as older data can be page compressed with no impact (archived data
should be read only).
Appendix I – CompressDatabase Stored Procedure