Всем привет!
В этой статье мы решили поделиться своими жизненными наблюдениями за проблемой производительности больших списков в SharePoint.
Итак, мы достаточно часто сталкиваемся с ситуациями, когда есть список на портале SharePoint, в котором хранятся и обрабатываются, например, заявки пользователей. Дополнительно всегда возникает желание сделать так, чтобы все заявки хранились в одном месте, с другой стороны — чтобы права на эти элементы были только у пользователя, подавшего заявку, и у группы или групп пользователей, которые участвуют в ее обработке.
Сначала немного терминологии:
ACL (Access Control List) – упорядоченный список принципалов, которые определяют права на элемент или группу элементов.
Security Scopes – набор пар групп или пользователь + ACL.
Рекомендации Microsoft
Microsoft рекомендует соблюдать достаточно простые правила: избегать количества одновременных Security Scopes более 5 000 на одну библиотеку документов или на один список.
Почему так? Откуда взялось ограничение? Для начала ответим на вопрос, каким образом MS SharePoint обрабатывает запрос к списку.
- Делает обращение к БД содержимого и читает:
a. Метаданные списка, т.е. определение списков полей и т.п.
b. Computed fields, определения обработчиков событий и много чего еще.
c. Все scope списка (точнее представления). - На стороне front-end определяет: в scope попадает пользователь.
- Вытаскивает уже непосредственно данные.
Такому подходу есть объяснения, и связаны они с поддержкой модели внешних поставщиков членства и ролей в первую очередь (AD, т.е. AD является наиболее широко распространенным поставщиком членства и ролей).
На практике можно видеть, что проблемы начинаются уже с 1000 элементов с уникальными правами.
С помощью этих SQL-запросов можно посчитать, сколько сейчас у вас Security Scopes:
SELECT [SiteId], [ScopeId], [RoleDefWebId], [WebId], [ScopeUrl], [Acl]
FROM [your Content DB].[dbo].[Perms] order by scopeurl
An even better query exposes the web URL
SELECT [fullurl],perms.[SiteId] , perms.[ScopeId], [RoleDefWebId], [WebId], [ScopeUrl], [Acl]
FROM [dbo].[Perms],[dbo].allwebs
where perms.WebId = allwebs.id
order by scopeurl
As an aside, you can see the number of security principals per scope, using this SQL:
select COUNT(ra.PrincipalId) as [Count],p.ScopeUrl from RoleAssignment ra with(nolock)
join Perms p with(nolock)
on p.SiteId = ra.SiteId and p.ScopeId = ra.ScopeId
group by p.ScopeUrl
order by p.ScopeUrl desc
Как Microsoft предлагает решать проблему:
- Использовать папки для инкапсуляции прав доступа на группы элементов с похожими правами.
- Разбивать на разные списки или библиотеки документов и использовать наследованные права.
- Никогда не выходить за лимит 5 000 уникальных права на листе.
К чему приводит нарушение этих рекомендаций:
- Снижение производительности на 20% на каждые 1 000 уникальных прав на листе.
- Программные ошибки при попытках разорвать права на элементе и выдать уникальные.
Для решений на базе MS SharePoint критически важно, чтобы ферма держала нагрузку, и вот почему.
Для разработчика нет поддержки транзакций. И наборы операций вида (т.е. если создать элемент, разорвать права, выдать на него права в MS SharePoint):
var item = list.AddItem(...);
// ...
item.Update();
item.BreakRoleInheritance(false);
item.RoleAssignments.Add(new SPRoleAssignment(...));
будут прерываться в неожиданных местах, просто потому что ферма не выдерживает нагрузки (т.е. не из-за логических ошибок).
Понятно, что рекомендации 2 и 4 носят более камерный характер и не решают задачу хранения элементов вместе и уникальных прав, поэтому остановимся на первом как самом действенном способе – использование папок.
Наш пример
Для примера возьмем список из реально работающей системы, в которой на текущий момент 132 073 элементов:
В ней 3 590 Security Scopes – выглядит примерно вот так:
Внутри списка 721 папка...:
… на которые выданы права следующим образом:
По данным Google Analytics, среднее время загрузки страницы менее секунды — при уровне посещений именно этого списка за этот период порядка 968 в день, в рабочее время.
Наш подход
В соответствии с нашим подходом права раздаются вот так:
public static void ProcessItem(SPListItem item, SPFolder folder)
{
var file = item.Web.GetFile(item.UniqueId);
// Внимание не используйте item.File т.к. он во многих случаях будет null
// http://msdn.microsoft.com/ru-ru/library/microsoft.sharepoint.splistitem.file.aspx
file.MoveTo(string.Format("{0}/{1}", folder.Url, item[SPBuiltInFieldId.LinkFilename]));
}
Также хочется добавить, что если наборы прав различаются не разрешениями на чтение видимости, а возможностью изменения, можно использовать Event Handler, который будет запрещать изменения.
class PermissionEventHandler : SPItemEventReceiver
{
public override void ItemUpdating(SPItemEventProperties properties)
{
var updatingAllowed = CheckUserHasPermission(properties.Web.CurrentUser, properties.ListItem);
// определить может ли пользователь изменять элемент
if (!updatingAllowed)
{
properties.Cancel = true;
properties.ErrorMessage = "Access denied";
}
}
public override void ItemAdding(SPItemEventProperties properties)
{
//...
}
//...
}
До изобретения этого подхода нам пришлось в течение 5 лет плотно заниматься SharePoint, съесть немало собак и кактусов. У него есть свои нюансы, но он абсолютно точно работает — опробовано уже на двух порталах.
Как всегда, рады были поделиться опытом!
Автор: eastbanctech