Ik werk voor een klant met een datawarehouse waarin de data één keer per dag ’s nachts wordt geladen. De rest van de dag verandert er niets in het datawarehouse. Na een paar jaar blijft de database maar groeien en worden rapporten steeds trager. Een ander probleem is dat ze Reporting Services als front-end tool gebruiken, met een paar rapporten die erg slecht presteren, ook na het tunen van indexen en queries. Dit artikel geeft je een methode om met caching de performanceproblemen op te lossen en je Reporting Services-rapport 100% sneller te maken!

We gebruiken een aantal zware rapporten in Microsoft Reporting Services om data aan de business users te tonen, en ze cachen is dan echt een uitkomst. Caching betekent dat de server een tijdelijke “snapshot” van het rapport in zijn cache bewaart, zodat het meteen verschijnt! De performancewinst is groot, maar er is één probleem. Ik weet niet precies wanneer de data “klaar is voor gebruik”, en ik moet de gecachte rapporten dynamisch verversen. Met dynamisch bedoel ik: op basis van parameters. Ik moet ook eerst de SSRS-cache opschonen.

In Microsoft Reporting Services zijn er twee opties om rapporten vooraf te laden: caching en snapshots. Een snapshot is als een vel papier, een statische versie van het rapport. Na het maken van de snapshot kun je niets meer aan de parameters veranderen. Caching werkt anders. Het kan verschillende “versies” van het rapport bewaren (hetzelfde rapport met verschillende parameterinstellingen) en die in TempDB opslaan tot ze verlopen. Een gecachte versie wordt uit de cache verwijderd als hij verloopt. Je kunt dingen op geen enkele manier “opnieuw cachen” (alleen met een geavanceerd cache refresh plan, dat valt buiten dit artikel). Als je de caching opnieuw draait, zoekt SSRS de versie in de database, ziet dat die al in de cache staat en laat de oude resultaten in de database staan. Dit doet SSRS op basis van de opgegeven parameters (fingerprint). Dit is heel belangrijk om te begrijpen. Een rapport blijft niet eeuwig in de cache. Het wordt verwijderd als het rapport is ingesteld om te verlopen, als het rapport wordt aangepast, als de parameters worden aangepast, als de credentials van de databron veranderen, als er andere uitvoeropties veranderen of als je het rapport verwijdert.

Houd bij Null Delivery dus in gedachten dat de kopie van het rapport in de cache verlopen moet zijn voordat je met deze methode een nieuwe kopie maakt. Ik raad aan de rapportcache te laten verlopen op een vast schedule, kort voordat deze subscription draait.

Er zijn twee methoden om de data van de cache of een snapshot op te halen (niet verversen, dat is een verkeerde term): op een vast tijdstip OF met een shared schedule. Zoals je ziet werkt geen van deze opties voor mij. Daarom heb ik wat beter gekeken welke opties ik heb om de cache handmatig te verversen.

Concept: we gebruiken twee shared schedules. Dit zijn dummy shared schedules. Aan deze shared schedules koppelen we de rapporten die we willen cachen/laten verlopen. De eerste gebruiken we om het verlopen van de rapportcache te starten. De tweede gebruiken we om de resultaten te cachen. Aan het eind van de load van ons datawarehouse starten we de shared schedules op een bepaald moment. Alle caching van de gekoppelde rapporten wordt verwijderd en opnieuw gevuld!

Ik neem je mee door de stappen om dit voor elkaar te krijgen!

Stap 1 - Maak twee dummy shared schedules
Je hebt de juiste rechten in SSRS manager nodig om de knop “Site Settings” boven in Report Manager te krijgen:

ssrs_site_settings

Klik op Site Settings, Schedules, New Schedule. Geef het een duidelijke naam, zoiets als

“DWH_SharedSchedule_ForCaching_DoNotChangeOrDelete” “DWH_SharedSchedule_ForExpireCaching_DoNotChangeOrDelete”

Hiermee activeren we de caching en het opschonen van de cache voor alle rapporten.

Shared_SS

Je kunt “Run Once” niet instellen met een tijdstip in het verleden, anders werkt het nooit. Dan verloopt de subscription meteen nadat de caching heeft gedraaid! Ik ontdekte dit omdat mijn caching niet werkte. Het maakt dubbele cacherecords in de database met dezelfde hashwaarde, allemaal verlopen. Mijn conclusie is dus dat SSRS het Shared Schedule controleert dat aan de “expire value” hangt, om te zien wanneer de cache moet verlopen. Is dit shared schedule al verlopen, dan verloopt de cache meteen zodra hij in de tabel is opgeslagen. De datum is dan 1970-01-01!

SQL
SELECT C.Name, EC.*
FROM ReportServerTempDB.dbo.ExecutionCache AS EC
INNER JOIN ReportServer.dbo.Catalog AS C ON EC.ReportID = C.ItemID

caching_expired

Belangrijk

Geef het Shared Schedule een geldige waarde. Verlopen shared schedules werken NIET!!. Maak er bijvoorbeeld een met een startdatum van gisteren en laat hem één keer per jaar draaien, op 31 december.

De Next run ligt in de toekomst, het schedule ook, en de kolom expired is leeg! Controleer dit goed!

shares_schedule_Settings

SSRS maakt voor dit schedule een agent job aan. Je ziet deze job in de Agent. De planning van deze job staat op het tijdstip dat je hebt ingesteld. Je kunt ook de step bekijken die SSRS maakt! Voor nu verwijderen we deze job. Hij vervuilt je agent jobs en we starten de job niet via de agent. We starten de job in SSIS, aan het eind van ons ETL-proces.

agent_job_2

agent_job_2

agent_job_1

Stap 2 - Stel een NULL Delivery Subscription in

Ga (via Report Manager) naar het SSRS-rapport dat slecht presteert. Dit rapport stellen we in voor caching via een NULL Delivery subscription.

ssrs_managere_report

Klik op het tabblad “Subscription” en maak een nieuwe data driven subscription van het type “Null Delivery Provider”.

Je hebt een SQL Server Enterprise-licentie nodig voor deze functie. Staat de knop er niet, dan heb je waarschijnlijk de Standard Edition.

ssrs_datadriven_1

ssrs_datadriven_2

Let op: je moet het wachtwoord elke keer invullen als je de subscription aanpast.

ssrs_datadriven_3

Zoals je ziet gebruik ik een view in de database. In deze view genereer ik alle parametercombinaties waarvoor ik het rapport wil cachen. Dat kan één regel zijn of heel veel regels. Voorbeeld:

ssrs_datadrive_view

Klik op Validate om te controleren of alles goed is. Klik op next, next.

datadrivensubscription_parameter_mapping

Koppel nu elke parameter uit de view of het SQL-commando aan de rapportparameter. Elke combinatie wordt gecachet. Elke keer dat je deze subscription aanpast, moet je het wachtwoord van de connectie invullen.

In de laatste stap koppelen we deze subscription aan het dummy shared schedule:

ssrs_datadriven_4

Stap 3 - Configureer de caching

Ga terug naar het scherm “Manage Report” en kies links “Processing Options”. Kies:

ssrs_processing_options

Stap 4 - De Shared Schedules draaien

We hebben nu een Data Driven Subscription van het type “NULL Delivery” ingesteld. Er wordt niets per mail verstuurd, het rapport wordt alleen gegenereerd om het te cachen. Nu moeten we het shared schedule een duwtje geven zodat het draait. Dat doe je met de procedure AddEvent op de Report Server-database. Eerst halen we de GUID van het dummy shared schedule op. Daarna voegen we het event SharedSchedule toe aan de event-tabel. SSRS leest deze tabel elke seconde en voert het event uit! Maar let op: schoon eerst de cache op van alle rapporten die via dit shared schedule verlopen.

SQL
-- Cleanup Cache
DECLARE @ExpiredScheduleID UNIQUEIDENTIFIER
DECLARE @ExpiredScheduleName AS VARCHAR(128) = 'DWH_SharedSchedule_ForExpireCaching_DoNotChangeOrDelete'
-- Get the Schedule identifier
SET @ExpiredScheduleID = (SELECT ScheduleID FROM ReportServer_BENL_DWH_1.dbo.SCHEDULE WHERE NAME = @ExpiredScheduleName)
-- pass the Schedule identifier to the Event insert procedure
EXEC ReportServer_BENL_DWH_1.dbo.AddEvent @EventType='SharedSchedule', @EventData=@ExpiredScheduleID
-- Execute a Shared Schedule:
DECLARE @ScheduleID UNIQUEIDENTIFIER
DECLARE @ScheduleName AS VARCHAR(128) = 'DWH_SharedSchedule_ForCaching_DoNotChangeOrDelete'
-- Get the Schedule identifier
SET @ScheduleID = (SELECT ScheduleID FROM ReportServer_BENL_DWH_1.dbo.SCHEDULE WHERE NAME = @ScheduleName)
-- pass the Schedule identifier to the Event insert procedure
EXEC ReportServer_BENL_DWH_1.dbo.AddEvent @EventType='SharedSchedule', @EventData=@ScheduleID

Stap 5- Testen

Dat is alles! Nu kun je testen of het werkt door het rapport te draaien. Het cachen van het rapport kan even duren, dus heb geduld! Op dit scherm zie je wanneer de subscription heeft gedraaid:

SSRS-subscription

Je kunt ook in de database controleren of het resultaat in de cache staat en wanneer het verloopt (veld AbsoluteExpiration).

SQL
SELECT C.Name, EC.*
FROM ReportServerTempDB.dbo.ExecutionCache AS EC
INNER JOIN ReportServer.dbo.Catalog AS C ON EC.ReportID = C.ItemID

Belangrijk

Houd er rekening mee dat de cache, na het instellen van “Cache a temporary copy of the report. Expire copy of report after a number of minutes” of met het shared schedule, na 9999 minuten verloopt en NIET als je een NULL Delivery opnieuw draait. Ik dacht dat SSRS de nieuwe resultaten zou cachen, maar dat doet het niet! SSRS bewaart een fingerprint van de parameterinstellingen in de historie naast het gecachte resultaat (kolom ParamsHash in de tabel ExectutionCache). Als je de subscription opnieuw draait, ziet SSRS dat het resultaat al in de cache staat, ook als er nieuwe data is. Het is heel belangrijk om eerst de cache op te schonen!

Let op: er is geen SQL-commando om de cache van een NULL Delivery Provider te verversen. Dat moet via een shared schedule dat de cache laat verlopen. Er is wel een commando om een cache refresh plan te verversen, maar wij doen de caching met de subscription. Je kunt de cache ook opschonen met dit commando (alle caching wordt verwijderd, ook snapshots):

SQL
DELETE ReportServer_TempDB.dbo.SnapshotData
WHERE SnapshotDataID IN (SELECT EC.SnapshotDataID
FROM ReportServer_BENL_DWH_1.dbo.Catalog C
INNER JOIN ReportServer_TempDB.dbo.ExecutionCache EC ON EC.ReportID = C.ItemID
WHERE LEFT(C.Path,8) = '/DWH_NL/')
DELETE ReportServer_TempDB.dbo.ExecutionCache
WHERE SnapshotDataID IN (SELECT EC.SnapshotDataID
FROM ReportServer_BENL_DWH_1.dbo.Catalog C
INNER JOIN ReportServer_TempDB.dbo.ExecutionCache EC ON EC.ReportID = C.ItemID
WHERE LEFT(C.Path,8) = '/DWH_NL/')

Je kunt ook nog wat extra controles doen in de SSRS-database om de cache-instellingen te zien:

SQL
SELECT *
FROM ReportServe.dbo.CachePolicy
ExpireFlags: 1 = Expire after XXX minutes,  2 = Using a Shared Schedule

Controleer in TempDB of het rapport in de cache staat:

SQL
SELECT *
FROM ReportServerTempDB.dbo.ExecutionCache

Nog een heel handige optie is om één NULL Delivery Subscription (of een andere subscription) te draaien met een T-SQL-statement:

SQL
EXEC [ReportServer].dbo.AddEvent @EventType = 'TimedSubscription', @EventData = 'E7F3A09D-8576-4EC7-A1DB-86E244E0A670'

Vervang de UID door de UID van de subscriptionID uit de tabel dbo.Subscription.

Update 12 nov 2013

Vandaag meldde een klant dat SSRS hem verkeerde resultaten liet zien. Na wat onderzoek ontdekte ik dat alle ETL-processen geladen waren en dat de rapportcache ververst was. Wat ging er mis? Mijn ETL-package om de cache te verversen zag er zo uit:

ETL-package om de SSRS-cache te verversen

Ik ontdekte dit toen ik in de execution log keek. De byte count voor de NULL-delivery subscription was 0. Dat betekent dat het rapport uit de cache kwam. Toen de NULL delivery werd geactiveerd, had SSRS dus al een fingerprint van de parameters (er was dus al cache).

Reporting Services-log

En hier is het SQL-statement waarmee je controleert of een NULL-Delivery Subscription heeft gedraaid. Dit is een voorbeeld voor één rapport. Controleer de rowcount! Is die 0, dan kwam het rapport uit de cache.

SQL
SELECT C.Name, EL.TimeStart, EL.TimeEnd,EL.*
FROM dbo.ExecutionLogStorage AS EL
INNER JOIN dbo.Catalog AS C ON EL.ReportID = C.ItemID
WHERE TimeStart > '2013-11-12'
AND C.Name = 'PS_ManagementRapportage Productiviteit'

Ik denk dat SSRS wat vertraging nodig heeft tussen het verwerken van de caching en het laten verlopen van de cache, omdat ik deze SQL vanuit een stored procedure draai. Dit is lastig te testen. Mijn oplossing voor nu is om alle cache te verwijderen, hard-coded. Dat doe je met deze SQL. In dit voorbeeld verwijder ik alleen de cache van de map DWH_NL.

SQL
DELETE ReportServerTempDB.dbo.ExecutionCache
WHERE SnapshotDataID IN (SELECT EC.SnapshotDataID
FROM ReportServer.dbo.Catalog C
INNER JOIN ReportServerTempDB.dbo.ExecutionCache EC ON EC.ReportID = C.ItemID
WHERE LEFT(C.Path,8) = '/DWH_NL/')

Update:

Ik heb een vertraging toegevoegd tussen de Expire Cache en het NULL Delivery shared schedule:

SQL
-- RUN SHARED SCHEDULE: FOREXPIRECACHING TO ACTIVATE ALL NULL DELIVERY SUBSCRIPTIONS
DECLARE @ExpiredScheduleID UNIQUEIDENTIFIER
DECLARE @ExpiredScheduleName AS VARCHAR(128) = 'DWH_SharedSchedule_ForExpireCaching_DoNotChangeOrDelete'
-- Get the Schedule identifier
SET @ExpiredScheduleID = (SELECT ScheduleID FROM ReportServer.dbo.SCHEDULE WHERE NAME = @ExpiredScheduleName)
-- pass the Schedule identifier to the Event insert procedure
EXEC ReportServer.dbo.AddEvent @EventType='SharedSchedule', @EventData=@ExpiredScheduleID
-- There can be an issue with caching if the expiration is not 100% complete. We set a WAIT
-- command to know sure that all cache has been expired.
--The format is hh:mi:ss.mmm.
WAITFOR DELAY '00:02:00.000';
-- RUN SHARED SCHEDULE: FORCACHING TO ACTIVATE ALL NULL DELIVERY SUBSCRIPTIONS
DECLARE @ScheduleID UNIQUEIDENTIFIER
DECLARE @ScheduleName AS VARCHAR(128) = 'DWH_SharedSchedule_ForCaching_DoNotChangeOrDelete'
SET @ScheduleID = (SELECT ScheduleID FROM ReportServer.dbo.SCHEDULE WHERE NAME = @ScheduleName)
EXEC ReportServer.dbo.AddEvent @EventType='SharedSchedule', @EventData=@ScheduleID

**Mogelijke problemen

**We gebruiken deze methode nu zeven jaar. Na een migratie naar een nieuwe server ontdekten we dat het cachemechanisme van SSRS niet meer werkte. Op de pagina met subscriptions in Report Manager leek de subscription goed te werken. Er stond dat alle subscriptions waren uitgevoerd. Als ik een rapport draaide, was het heel, heel traag. Toen ik een rapport opende, controleerde ik de tabel dbo.ExecutionCache. Er verscheen één record in de tabel, dus het was gecachet. Toen ik hetzelfde rapport opnieuw opende, werd er een nieuw record met dezelfde data gecachet! Best vreemd! Na lang zoeken ontdekte ik dat het verlopen van de cache aan een verlopen schedule hing!

Als je de cache van een subscription koppelt aan een verlopen shared schedule, verwijdert Reporting Services de cache automatisch na XXX minuten! Het beste wat je kunt doen is het shared schedule een jaar in de toekomst zetten, zodat het niet verloopt!

Veelgestelde vragen

Hoe ververs ik de cache van Reporting Services na het laden van een datawarehouse?

Maak twee dummy shared schedules: een om de cache te laten verlopen en een om hem te vullen. Start ze aan het eind van de load met de AddEvent-procedure in de Report Server-database.

Wat is het verschil tussen caching en snapshots in SSRS?

Een snapshot is een statische versie van een rapport. Na het maken kun je de parameters niet meer veranderen. Caching kan verschillende versies van hetzelfde rapport met verschillende parameters in TempDB bewaren, tot de cache verloopt.

Hoe start ik een SSRS shared schedule met SQL?

Haal de ScheduleID op naam uit de tabel Schedule. Voer daarna AddEvent uit op de Report Server-database met EventType SharedSchedule en EventData op die ID. SSRS leest de event-tabel elke seconde.

Welke SQL Server-editie heb ik nodig voor een data driven subscription met Null Delivery?

Je hebt SQL Server Enterprise nodig. Staat de knop voor een data driven subscription er niet, dan heb je waarschijnlijk de Standard Edition.

Hoe controleer ik of een SSRS-rapport in de cache staat en wanneer die verloopt?

Join ReportServerTempDB.dbo.ExecutionCache met ReportServer.dbo.Catalog op ReportID = ItemID. Het veld AbsoluteExpiration laat zien wanneer de cache verloopt.

12 reacties

Je e-mailadres wordt niet getoond. Een foto komt van Gravatar, als je die hebt.

  1. Thanks for your blog post which shines a lot of light on this very effective (but poorly-documented) functionality in SSRS.

    I also noticed that deploying a report from Visual Studio also clears all caches for that report.

  2. Ronald KraijesteijnAuteur

    Thanks Mike. There is hardly NO documentation available about this subject. It was a nice job to discover how everything works under water. I hope this article helps you to get things done!

  3. Thanks a lot for putting your effort into a blog. It helped a lot :) Your solution is working great.
    I have no idea why such a basic feature like cache Invalidation/refresh cannot be triggered easily by standard.
    In addition to your "NULL delivery provider" I'm using your method successfully for cache refresh plans and I think I'll use it in the near future for our subscriptions too (assure that subscriptions go out after ETL has finished).

    Keep it up!
    Chris

  4. Lohic Beneyze

    Hi Ronald, thanks a lot for this clear blog with full tips. I've implemented it and it works lovelly.

    Maybe instead of using dbo.ExecutionLogStorage, the DMV ExecutionLog3 can be used. There is a column "Source" that indicates if the report has been rendered from Cache or Live or Session (Indicates a follow up request within an already established session)

    SELECT EL.ItemPath, EL.TimeStart, EL.TimeEnd,EL.*
    FROM dbo.ExecutionLog3 AS EL
    WHERE TimeStart > '2013-11-12'
    AND ItemPath like '%PS_ManagementRapportage Productiviteit%'

    I've just one issue and I'm wondering if you run into it.
    I've configured the caching for 2 reports: the reports are exactly the same but with 2 different names.
    When I execute the addevent for the shared Schedule ForCaching and check if the reports has ReportServerTempDB.dbo.ExecutionCache
    Only 1 report appear.

    Do you have any idea why the other report is not being cached?

    I'm using SQL Server 2012 SP1. The reports are deployed on a Sharepoint 2013 site.

    Thanks in advance.

  5. Ronald KraijesteijnAuteur

    Hi Lohic,

    It's been a while since I was programming this piece. I have not run into this issue. Every report has an unique ID so even when they are the same, they are unique in the database.

  6. Donna Hussey

    Awesome article, just what I was looking for. Thank you.

  7. Ronald KraijesteijnAuteur

    You're welcome. It's really cool to manipulate thing in the SSRS background. You can build your own subscription system in a front/backoffice application and "push" thing to the SSRS database, really cool.

  8. Glad I found this post, thanks for taking the time to clearly walk through it! We have a very similar setup, where we "expire" the shared schedules as a last step in our ETL processing. And, similar to what you've experienced, we occasionally see cases where the cache is not actually cleared out, even though the schedule was expired. We added a delay step, but this didn't always resolve the issue. After further investigation, we're seeing deadlocks on the report server after we call the "AddEvent" sproc.

    The deadlock info shows that the delete statement in "FlushReportFromCache" is always the victim; this sproc gets called somewhere after a shared schedule event is added to the events table. The circular lock involves the "IX_SnapshotDataID" index in the dbo.ExecutionCache table and the "IX_SnapshotData" index in the dbo.SnapshotData table. The deadlocks result in old data remaining in TempDB.

    Have you experienced this at all? If so, do you have a solution to get around these deadlocks? Thanks!

  9. Hi Chris,

    Thanks for the comment's. I havent'faced this issue myself so no experience with fixing this thing. Did you try to delete the cache record "hard" with a delete statement?

  10. Thanks for the quick response. We were trying to avoid a "hard" delete if possible, but I'll keep that approach in my back pocket if I can't figure anything else out. If I find a solution, I'll post it in the comments here.

  11. Hi

    How to clear the cache before run the share schedule.
    Is it just run the DELETE statements or some other method and why we need first dummy share schedule (for DWH_SharedSchedule_ForExpireCaching_DoNotChangeOrDelete )?

    Do we need to put Delete statements in this Data driven DWH_SharedSchedule_ForExpireCaching_DoNotChangeOrDelete subscription?

    thanks

  12. You must attach the subscription cache expiration option to the shared schedule DWH_SharedSchedule_ForExpireCaching_DoNotChangeOrDelete and trigger that schedule. The cache will be cleared!