Overview
Several QBO modules leverage the Attachment module, including qbo.Import, qbo.Contact, qbo.Debt and qbo.Score. Over the course of time, it the number of attachment accumulated by these modules can cause the Attachment module's performance to decrease, and incur unwanted storage expenses. To manage such issues, we can leverage archiving and expiration.
Attachment Templates offer fields that determine when to automatically archive (move to cheap storage) or expire (delete), based on the age of the attachment. Modules that use attachments "under the hood" can be configured to automatically use configured attachment templates to drive archiving and expiration. To configure:
From Design > Documents, create a template configured with the archive and expiration fields set to whatever values you deem appropriate
in the examples below, the template created is called "SystemAttachmentTemplate"
From Design > Configuration > Queuing, create a maintenance queue to ensure archiving and expiration jobs don't interfere with other processing
Consider updating existing Attachment rows to populate the AttachmentTemplateID, ExpirationDate and ArchiveDate fields
From Design > Configuration > {Module} > Settings, set the Default*AttachmentTemplate values to the template configured in step 1
Modules that leverage such settings include: ImportFile, ServiceRequest, ProfessionalLicense, CreditReport, Bucket, BucketItem, Score
Settings related to attachment templates:
qbo.Import
DefaultPreTransformAttachmentTemplate
DefaultPostTransformAttachmentTemplate
DefaultServiceRequestAttachmentTemplate
DefaultImportResultAttachmentTemplate
DefaultBucketAttachmentTemplate
DefaultBucketItemAttachmentTemplate
qbo.Contact
DefaultProfessionalLicenseAttachmentTemplate
qbo.Credit
DefaultCreditReportAttachmentTemplate
qbo.Score
DefaultScoreAttachmentTemplate
Empty Folder Cleanup
A few new settings have been introduced to the UNC File Object:
DeleteParentDirectory - bool (defaults to false). If set to true and the folder this attachment is in is empty, the folder will be deleted (Assuming it's not the file object assembly's root folder and not a protected folder name)
DeleteParentDirectoryDepth - int (defaults to 1). This will move up the directory tree deleting all empty folders until it finds a non-empty, non-protected, non-root folder.
DeleteParentProtectedFolderNames - string (defaults to empty). Forces protection of any folders with these names from ever being deleted. These are | delimited. E.G. "IN|OUT"
If you use the suggested FileNameFormat (/{Year}/{Month}/{Day}/{Object}/{ObjectID}/{Guid}.xml), it is recommended to have a DeleteParentDirectoryDepth of 6.
Sample File Object:
<ConfigurationEntryItem>
<ConfigurationEntry>Standard BLOB Storage</ConfigurationEntry>
<Source>FileObject.config</Source>
<ConfigurationType>qbo.Attachment.Configuration.FileObjectCollection</ConfigurationType>
<ConfigurationKey>Standard BLOB Storage</ConfigurationKey>
<SubscriberID>qbs.quandis.net-FileObject-Standard BLOB Storage</SubscriberID>
<ConfigurationXml>
<FileObject Name="Standard BLOB Storage"
Type="qbo.Attachment.FileObjects.UNCFile, qbo.Attachment"
Uri="//HDCShared/UATFileStore/devqbs.quandis.net/"
Settings="DeleteParentDirectory=true&DeleteParentDirectoryDepth=6&DeleteParentProtectedFolderNames=IN|OUT"
Compression="false"/>
</ConfigurationXml>
</ConfigurationEntryItem>
Data Listeners
The following sample XML will create jobs (data listeners) to archive and expire attachments based on the template settings. These data listeners are part of source-controlled Attachment.config, so this is for illustration purposes or overrides only.
Archiving
<ConfigurationEntryItem>
<ConfigurationEntry>Attachment/ArchiveAttachments</ConfigurationEntry>
<Source>Attachment.config</Source>
<ConfigurationType>qbo.Application.Configuration.DataListenerCollection</ConfigurationType>
<ConfigurationKey>ArchiveAttachments</ConfigurationKey>
<ConfigurationXml>
<Listener Name="ArchiveAttachments" Statement="ArchiveFileSearch" Class="Attachment" Operation="Archive" Parameters="AttachmentID={AttachmentID}&UpdatedDate={UpdatedDate}" Async="False" StopOnError="False" Status="" />
</ConfigurationXml>
</ConfigurationEntryItem>
Expiration
<ConfigurationEntryItem>
<ConfigurationEntry>Attachment/ExpireAttachments</ConfigurationEntry>
<Source>Attachment.config</Source>
<ConfigurationType>qbo.Application.Configuration.DataListenerCollection</ConfigurationType>
<ConfigurationKey>ExpireAttachments</ConfigurationKey>
<ConfigurationXml>
<Listener Name="ExpireAttachments" Statement="ExpireFileSearch" Class="Attachment" Operation="Expire" Parameters="AttachmentID={AttachmentID}&UpdatedDate={UpdatedDate}" Async="False" StopOnError="False" Status="" />
</ConfigurationXml>
</ConfigurationEntryItem>
Queue
The following sample XML will create a system maintenance queue to run the jobs above against.
<ConfigurationEntryItem>
<ConfigurationEntry>SystemMaintenance</ConfigurationEntry>
<Source>Queuing.config</Source>
<ConfigurationType>qbo.Application.Configuration.QueueCollection</ConfigurationType>
<ConfigurationKey>SystemMaintenanceQueue</ConfigurationKey>
<ConfigurationXml>
<Queue Name="SystemMaintenance"
Type="qbo.Queue.MSSQL.ServiceBroker, qbo.Queue.MSSQL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
TypeLabel="qbo.Queue.MSSQL.ServiceBroker"
Uri="SystemMaintenance"
Threads="1"
RetryCount="1"
RetryIntervalType="None"
ThrottleIntervalType="None"
Schedule="Queue - Business Hours"
Status="Started"
SuccessLog="QueueLog"
FailureLog="QueueLog"
Description="System Maintenance Queue"
ImpersonateType="QueuedUser" />
</ConfigurationXml>
</ConfigurationEntryItem>
System Attachment Template
The following sample XML will create a template that can be used by the application settings in other modules. Note that you should review and alter the ArchiveFileObjectAssembly value to a valid entry for your system.
<AttachmentTemplateItem>
<AttachmentTemplate>SystemAttachmentTemplate</AttachmentTemplate>
<Repeatable>Many</Repeatable>
<ExpirationScale>0</ExpirationScale>
<Expiration>14</Expiration>
<FileObjectAssembly>Standard BLOB Storage</FileObjectAssembly>
<FileNameFormat>/{Year}/{Month}/{Day}/{Object}/{ObjectID}/{Guid}.xml</FileNameFormat>
<ArchiveScale>2</ArchiveScale>
<Archive>1</Archive>
<ArchiveObjectAssembly>AmazonS3</ArchiveObjectAssembly>
<ExtranetEnabled>None</ExtranetEnabled>
<CustomStatusList>False</CustomStatusList>
<CustomTypeList>False</CustomTypeList>
</AttachmentTemplateItem>
DB Statements
These statemnents are part of source-controlled Attachment.config, so this is for illustration purposes or overrides only.
<ConfigurationEntryItem>
<ConfigurationEntry>Attachment/ArchiveFileSearch</ConfigurationEntry>
<Source>Attachment.config</Source>
<ConfigurationType>qbo.Application.Configuration.DbStatementCollection</ConfigurationType>
<ConfigurationKey>ArchiveFileSearch</ConfigurationKey>
<ConfigurationXml>
<Statement Name="ArchiveFileSearch" ReturnType="DataReader" Permission="{Table}ArchiveFileSearch" Timeout="1200" AllowInheritance="true" Query="
SELECT TOP {Param.Batch} AttachmentID, Attachment.ArchiveScheduled UpdatedDate FROM Attachment
INNER JOIN AttachmentTemplate
ON Attachment.AttachmentTemplateID = AttachmentTemplate.AttachmentTemplateID
WHERE ArchiveScheduled < GETDATE()
AND Attachment.FileObjectAssembly <> AttachmentTemplate.ArchiveObjectAssembly
AND ArchivalDate IS NULL
AND Attachment.ArchiveScheduled >= '{Param.FromDate}'
AND ISNULL(Attachment.Status, '') <> 'Error'
ORDER BY Attachment.ArchiveScheduled
">
<Parameters>
<Parameter Name="FromDate" DbType="DateTime" />
<Parameter Name="Batch" DbType="Int32" Value="500" />
</Parameters>
</Statement>
</ConfigurationXml>
</ConfigurationEntryItem>
<ConfigurationEntryItem>
<ConfigurationEntry>Attachment/ExpireFileSearch</ConfigurationEntry>
<Source>Attachment.config</Source>
<ConfigurationType>qbo.Application.Configuration.DbStatementCollection</ConfigurationType>
<ConfigurationKey>ExpireFileSearch</ConfigurationKey>
<ConfigurationXml>
<Statement Name="ExpireFileSearch" ReturnType="DataReader" Permission="{Table}ExpireFileSearch" Timeout="1200" AllowInheritance="true" Query="
SELECT TOP {Param.Batch} AttachmentID, Attachment.ExpirationScheduled UpdatedDate FROM Attachment
WHERE ExpirationScheduled < GETDATE()
AND ExpirationDate IS NULL
AND Attachment.ExpirationScheduled >= '{Param.FromDate}'
AND ISNULL(Attachment.Status, '') <> 'Error'
ORDER BY Attachment.ExpirationScheduled
">
<Parameters>
<Parameter Name="FromDate" DbType="DateTime" />
<Parameter Name="Batch" DbType="Int32" Value="500" />
</Parameters>
</Statement>
</ConfigurationXml>
</ConfigurationEntryItem>
Schedule Your Jobs
Note the {ScheduleID} is the value of ScheduleName in the parameters (not the actual schedule name).
http://localhost/Attachment/Attachment.ashx/Schedule/Listen?Listeners=ArchiveAttachments&ScheduleName={ScheduleID}&QueueName=SystemMaintenance
http://localhost/Attachment/Attachment.ashx/Schedule/Listen?Listeners=ExpireAttachments&ScheduleName={ScheduleID}&QueueName=SystemMaintenance