Кэширование уровня сессии в Reporting Services

С момента своего выхода в начале 2004 г. Розетта обладала способностью кэшировать отчеты. Существовало три уровня кэширования – уровня сессии; собственно, кэширование отчета и генерация последовательности мгновенных снимков. В 2008 R2 к ним добавилось кэширование датасетов. Сначала освежим в памяти первые три. Кэширование уровня сессии происходит автоматически и отключить его нельзя. Как известно, процесс получения отчета занимает три этапа. На первом Reporting Service лезет в источники данных и выполняет датасетные запросы. На втором читает оставшийся rdl, чтобы понять, как устроен отчет, какие где у него текстбоксы и табликсы и как в них напхать полученные на первом этапе данные. На третьем отрисовывает все это хозяйство в заказанный формат, будь то HTML, Excel, Word, pdf и т.д., так называемый рендеринг. Полуфабрикат между вторым и третьим этапами сохраняется в таблицы ReportServerTempDB. В таблицу SnapshotData ложится информация о выполнении отчета: время, параметры и т.д. Сам отчет делится на куски. Первый кусок (chunk) - это метаданные, второй - скомпилированное определение, третий - информация о разбиении по страницам и т.д. Раньше куски хранились в таблице ChunkData, сейчас она осталась, но пустует. Чтобы окончательно всех запутать, каждый кусок теперь дробится еще на сегменты. Куски лежат в таблице SegmentedChunk, сегменты - в таблице Segment, соответствие сегментов кускам - в таблице ChunkSegmentMapping. Полуфабрикат в виде бинарщины размазан по сегментам в поле Content. Разумеется, структура нигде не документирована, так что разобраться со всем этим решительно невозможно, если не найти и не почитать патентную заявку "Optimistic Versioning Concurrency Scheme for Database Streams". Из нее следует, что дробление кусков на сегменты сделано с целью уменьшения конфликтов и расхода места для обеспечения версионности при многопользовательской работе. В заявке иллюстрируется, как к куску С1 (таблица SegmentedChunk(VersionID, ChunkID), запись {1, C1}), побитому на сегменты Seg1 и Seg2 (таблица ChunkSegmentMapping(ChunkID, SegmentID, StartByte), записи {C1, Seg1, 0}, {C1, Seg2, 32000}), несущих в себе данные полуфабриката (таблица Segment(SegmentID, Content), записи {Seg1, 0x00AA}, {Seg2, 0x00BB}) обращается новый пользователь и его меняет. Предположим, он меняет его не весь, а что-нибудь в Seg2. Измененный сегмент добавляется в таблицу Segment(SegmentID, Content) - записи стали {Seg1, 0x00AA}, {Seg2, 0x00BB}, {Seg3, 0x00BС}. Создается новая версия 2 куска в таблице SegmentedChunk(VersionID, ChunkID) - записи стали {1, C1}, {2, C2}. Ей сопоставляется изменившийся сегмент - таблица ChunkSegmentMapping(ChunkID, SegmentID, StartByte), записи {C1, Seg1, 0}, {C1, Seg2, 32000}, {C2, Seg1, 0}, {C2, Seg3, 32000}. Таким образом, каждой пользовательской сессии соответствует своя версия куска, при этом они шарят неизменившиеся сегменты. В таком вот аксепте.

Продеплойте и выполните отчет Report_SQL из предыдущего поста на Report Server.

Рис. 1

Не отходя от кассы, экспортните отчет в какой-нибудь другой формат:

Рис. 2

Посмотрите, что осело в ReportServer..ExecutionLog. Кстати, теперь это не таблица, а вьюшка. А еще лучше посмотрите вьюху ExecutionLog3:

select top 2 ItemPath, RequestType, Format, TimeDataRetrieval, TimeProcessing, TimeRendering, Source, ByteCount, TimeStart, TimeEnd 
from ReportServer.dbo.ExecutionLog3 order by TimeStart desc

Скрипт 1

Рис. 3

Обратите внимание, что во втором случае (первая запись) время выполнения датасетов нулевое, а время процессинга отчета близкое к нулю по сравнению с первой записью. Фактически - это просто время выборки данных из вышеупомянутых таблиц ReportServerTempDB без какой-либо дополнительной умственной работы. Что означает, что при экспорте в Excel была использована закэшированная версия отчета, оставшаяся после первого выполнения, на что недвусмысленно указывает поле Source. Источником при втором выполнении является Session (Cache), т.е. данные текущей сессии, в отличие от живого выполнения в первом разе. Выполните скрипт

select * from ReportServerTempDB.dbo.SegmentedChunk 
select * from ReportServerTempDB.dbo.ChunkSegmentMapping 
select * from ReportServerTempDB.dbo.Segment 
select * from ReportServerTempDB.dbo.SnapshotData

Скрипт 2

Рис. 4

Это лежит в ReportServerTempDB закэшированный отчет.

select 
sess.SessionID, sess.CreationTime, sess.Expiration, sess.IsPermanentSnapshot, sess.ReportPath, sess.Timeout, 
sd.SnapshotDataID, sd.CreatedDate, sd.ExpirationDate, sd.QueryParams, sd.EffectiveParams, sd.PageCount, sd.IsCached, 
sc.ChunkId, sc.ChunkName, sc.Version, 
csm.StartByte, csm.LogicalByteCount, csm.ActualByteCount, 
s.SegmentId, s.Content from ReportServerTempDB.dbo.SessionData sess 
join ReportServerTempDB.dbo.SnapshotData sd on sess.SnapshotDataID = sd.SnapshotDataID 
join ReportServerTempDB.dbo.SegmentedChunk sc on sd.SnapshotDataID = sc.SnapshotDataID 
join ReportServerTempDB.dbo.ChunkSegmentMapping csm on sc.ChunkId = csm.ChunkId 
join ReportServerTempDB.dbo.Segment s on csm.SegmentId = s.SegmentId

Скрипт 3

SessionID CreationTime Expiration IsPermanent
Snapshot
ReportPath Timeout SnapshotDataID IsCached ChunkId ChunkName Start
Byte
Logical
Byte
Count
Actual
Byte
Count
SegmentId
wqsaa43v0qve
3sev2ornvpi5
09/04/2010 16:37:27 09/04/2010 16:48:07 0 /Report_SQL 600 50AB9B27-6C62-4FC7-A7CC-9BAE9897976C 0 E3FCBEA9-D443-DF11-A63F-00155D00011D GroupTree 0 20848 6059 E6FCBEA9-D443-DF11-A63F-00155D00011D
wqsaa43v0qve
3sev2ornvpi5
09/04/2010 16:37:27 09/04/2010 16:48:07 0 /Report_SQL 600 50AB9B27-6C62-4FC7-A7CC-9BAE9897976C 0 E4FCBEA9-D443-DF11-A63F-00155D00011D Metadata 0 1524 1180 E5FCBEA9-D443-DF11-A63F-00155D00011D
wqsaa43v0qve
3sev2ornvpi5
09/04/2010 16:37:27 09/04/2010 16:48:07 0 /Report_SQL 600 50AB9B27-6C62-4FC7-A7CC-9BAE9897976C 0 DFFCBEA9-D443-DF11-A63F-00155D00011D DataChunkx4 0 20691 4052 E0FCBEA9-D443-DF11-A63F-00155D00011D
wqsaa43v0qve
3sev2ornvpi5
09/04/2010 16:37:27 09/04/2010 16:48:07 0 /Report_SQL 600 50AB9B27-6C62-4FC7-A7CC-9BAE9897976C 0 E1FCBEA9-D443-DF11-A63F-00155D00011D PaginationInfo 0 72 174 E2FCBEA9-D443-DF11-A63F-00155D00011D

Я сознательно опустил некоторые поля, т.к. опасался, что ввиду малой ширины страниц весь результат сюда не влезет. В частности, CreatedDate и.ExpirationDate для снэпшота, т.к. они совпадают с CreationTime и Expiration для сессии. Очевидно, что Expiration более или менее = CreationTime + Timeout. SessionTimeout для сессии берется из конфигурационных настроек сервера отчетности в таблице ReportServer.. ConfigurationInfo. Их можно в ней править:

Рис. 5

Поскольку это кэширование уровня сессии, то для каждой сессии Reporting Services создается свой кэш. Информация по открытым сессиям хранится в таблице ReportServerTempDB.dbo.SessionData. Сейчас там видна всего одна текущая сессия:

select SessionID, SnapshotDataID, IsPermanentSnapshot, ReportPath, EffectiveParams, CreationTime, Timeout, Expiration from ReportServerTempDB.dbo.SessionData

Рис. 6

Сейчас я откроую браузер на хосте, зайду с него на сайт Reporting Services на виртуалке и выполню тот же самый отчет. Вот, что мы наблюдаем в таблице SessionData теперь:

Рис. 7

Появилась новая строчка с новым SessionID и, соответственно, со своим SnapshotID, то есть со своим кэшем - сравните с Рис. 4 и Скрипт 3, где SnapshotID был везде один, потому что там была всего одна сессия. Только не нужно пытаться сопоставить между собой их значения – поскольку статья писалась урывками, то и SessionID, и SnapshotID на рис.6-7 уже совсем другие. В идеале, конечно, можно считать, что SessionID = yaxejvjrsueha4uoqls2cl55, это wqsaa43v0qve3sev2ornvpi5, а SnapshotID = DAEB8C62-CD39-402D-BEBE-12BCE2015989, это 50AB9B27-6C62-4FC7-A7CC-9BAE9897976C.

По истечении сессии будут запущены хранимые процедуры, которые приберут проэкспайрившуюся сессию вместе с кэшем отчета из ReportServerTempDB.

Автор: Алексей Шуленин