2012-04-24 3 views
0

Я думаю о кэшировании разрешений для каждого пользователя на нашем сервере приложений. Это хорошая идея использовать SqlCacheDependency для каждого пользователя?Является ли использование SqlCacheDependency для пользователя хорошей идеей?

Запрос будет выглядеть следующим образом

SELECT PermissionId, PermissionName From Permissions Where UserId = @UserId 

Таким образом, я знаю, если какой-либо из этих записей изменится, то, чтобы очистить свой кэш для этого пользователя.

ответ

5

Если вы читаете how Query Notifications work, вы увидите, почему создание многих запросов зависимостей с помощью одного шаблона запроса является хорошей практикой. Для веб-приложения, которое подразумевается в том, что вы используете SqlCacheDependency, а не SqlDependency, то, что вы планируете делать, должно быть в порядке. Если вы используете Linq2SQL вы также можете попробовать LinqToCache:

var queryUsers = from u in repository.Users 
     where u.UserId = currentUserId 
     select u; 
var user= queryUsers .AsCached("Users:" + currentUserId.ToString()); 

Для жирной клиентского приложения не будет ОК. Не из запроса на-SE, а потому, что SqlDependency в целом является проблематичным с большим количеством клиентов, подключенных (он блокирует a worker thread для каждого домена приложения подключено):

SqlDependency был разработан для использования в ASP. NET или средний уровень услуг, где имеется относительно небольшое количество серверов, имеющих зависимостей, активных по отношению к базе данных. Он не был предназначен для использования в клиентских приложениях, где сотни или тысячи клиентов компьютеров имели бы объекты SqlDependency, настроенные для одного сервера базы данных .

Обновлено

Вот тот же тест, как @usr сделал на своем посту. Полный C# код:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Data.SqlClient; 
using DependencyMassTest.Properties; 
using System.Threading.Tasks; 
using System.Threading; 

namespace DependencyMassTest 
{ 
    class Program 
    { 
     static volatile int goal = 50000; 
     static volatile int running = 0; 
     static volatile int notified = 0; 
     static int workers = 50; 
     static SqlConnectionStringBuilder scsb; 
     static AutoResetEvent done = new AutoResetEvent(false); 

     static void Main(string[] args) 
     { 
      scsb = new SqlConnectionStringBuilder(Settings.Default.ConnString); 
      scsb.AsynchronousProcessing = true; 
      scsb.Pooling = true; 

      try 
      { 
       SqlDependency.Start(scsb.ConnectionString); 

       using (var conn = new SqlConnection(scsb.ConnectionString)) 
       { 
        conn.Open(); 

        using (SqlCommand cmd = new SqlCommand(@" 
if object_id('SqlDependencyTest') is not null 
    drop table SqlDependencyTest 

create table SqlDependencyTest (
    ID int not null identity, 
    SomeValue nvarchar(400), 
    primary key(ID) 
) 
", conn)) 
        { 
         cmd.ExecuteNonQuery(); 
        } 
       } 

       for (int i = 0; i < workers; ++i) 
       { 
        Task.Factory.StartNew(
         () => 
         { 
          RunTask(); 
         }); 
       } 
       done.WaitOne(); 
       Console.WriteLine("All dependencies subscribed. Waiting..."); 
       Console.ReadKey(); 
      } 
      catch (Exception e) 
      { 
       Console.Error.WriteLine(e); 
      } 
      finally 
      { 
       SqlDependency.Stop(scsb.ConnectionString); 
      } 
     } 

     static void RunTask() 
     { 
      Random rand = new Random(); 
      SqlConnection conn = new SqlConnection(scsb.ConnectionString); 
      conn.Open(); 

      SqlCommand cmd = new SqlCommand(
@"select SomeValue 
    from dbo.SqlDependencyTest 
    where ID = @id", conn); 
      cmd.Parameters.AddWithValue("@id", rand.Next(50000)); 

      SqlDependency dep = new SqlDependency(cmd); 
      dep.OnChange += new OnChangeEventHandler((ob, qnArgs) => 
      { 
       Console.WriteLine("Notified {3}: Info:{0}, Source:{1}, Type:{2}", qnArgs.Info, qnArgs.Source, qnArgs.Type, Interlocked.Increment(ref notified)); 
      }); 

      cmd.BeginExecuteReader(
       (ar) => 
       { 
        try 
        { 
         int crt = Interlocked.Increment(ref running); 
         if (crt % 1000 == 0) 
         { 
          Console.WriteLine("{0} running...", crt); 
         } 
         using (SqlDataReader rdr = cmd.EndExecuteReader(ar)) 
         { 
          while (rdr.Read()) 
          { 
          } 
         } 
        } 
        catch (Exception e) 
        { 
         Console.Error.WriteLine(e.Message); 
        } 
        finally 
        { 
         conn.Close(); 
         int left = Interlocked.Decrement(ref goal); 

         if (0 == left) 
         { 
          done.Set(); 
         } 
         else if (left > 0) 
         { 
          RunTask(); 
         } 
        } 
       }, null); 

     } 

    } 
} 

После 50k подписки устанавливается (занимает около 5 минут), вот статистика ИО одной вставки:

set statistics time on 
insert into Test..SqlDependencyTest (SomeValue) values ('Foo'); 

SQL Server parse and compile time: 
    CPU time = 0 ms, elapsed time = 0 ms. 

SQL Server Execution Times: 
    CPU time = 16 ms, elapsed time = 16 ms. 

Вставка 1000 строк занимает около 7 секунд, что включает в себя запуск нескольких сотен уведомлений. Загрузка процессора составляет около 11%. Все это на моем ThinkPad T420s.

set nocount on; 
go 

begin transaction 
go 
insert into Test..SqlDependencyTest (SomeValue) values ('Foo'); 
go 1000 

commit 
go 
+0

Я боюсь, что кэширование всех этих пользовательских данных даже для отключенных пользователей может быть слишком большим, любые альтернативы? –

+0

SqlCacheDependency имеет политики выселения кеша. –

+0

Это в основном тот же результат, что и мой тест. Создание вашей зависимости многопоточно, поэтому оно быстрее. Возможно, в 4 раза? 7s для 1000 вставок * ужасные *. 11% cpu = 1 ядро ​​при 100%. – usr

0

документация говорит:

SqlDependency был разработан для использования в ASP.NET или среднего уровня услуг, где существует относительно небольшое число серверов, имеющих зависимости активных по отношению к базе данных. Он не был предназначен для использования в клиентских приложениях , , где сотни или тысячи клиентов компьютеров имели бы объекты SqlDependency, настроенные для одного сервера базы данных .

Он говорит нам не открывать тысячи зависимостей от кеша. Вероятно, это вызовет проблемы с ресурсами на SQL Server.

Есть несколько альтернатив:

  1. иметь зависимость каждой таблицы
  2. есть 100 зависимостей для каждой из таблиц, по одному на каждый процент строк. Это должно быть приемлемым числом для SQL Server, но вам нужно только сделать недействительным 1% кеша.
  3. Имейте триггер, который выводит идентификатор всех строк изменений в таблицу протоколирования. Создайте зависимость от этой таблицы и прочитайте идентификаторы. Это точно скажет, какие строки изменились.

Для того, чтобы выяснить, если SqlDependency подходит для массового использования я сделал тест:

 static void SqlDependencyMassTest() 
     { 
      var connectionString = "Data Source=(local); Initial Catalog=Test; Integrated Security=true;"; 
      using (var dependencyConnection = new SqlConnection(connectionString)) 
      { 
       dependencyConnection.EnsureIsOpen(); 

       dependencyConnection.ExecuteNonQuery(@" 
if object_id('SqlDependencyTest') is not null 
    drop table SqlDependencyTest 

create table SqlDependencyTest (
    ID int not null identity, 
    SomeValue nvarchar(400), 
    primary key(ID) 
) 

--ALTER DATABASE Test SET ENABLE_BROKER with rollback immediate 
"); 

       SqlDependency.Start(connectionString); 

       for (int i = 0; i < 1000 * 1000; i++) 
       { 
        using (var sqlCommand = new SqlCommand("select ID from dbo.SqlDependencyTest where ID = @id", dependencyConnection)) 
        { 
         sqlCommand.AddCommandParameters(new { id = StaticRandom.ThreadLocal.GetInt32() }); 
         CreateSqlDependency(sqlCommand, args => 
          { 
          }); 
        } 

        if (i % 1000 == 0) 
         Console.WriteLine(i); 
       } 
      } 
     } 

Вы можете увидеть количество зависимостей, созданных свиток с помощью консоли. Это замедляется очень быстро. Я не делал формальных измерений, потому что не было необходимости доказывать это.

Кроме того, план выполнения простой вставки в таблицу показывает 99% стоимости, связанной с поддержанием зависимостей 50k.

Заключение: не работает вообще для использования в производстве. После 30 минут у меня создано 55k зависимостей. Машина на 100% процессор все время.

+0

сотни зависимостей! = Сотни * компьютеров * –

+0

Я все еще думаю, что каждая зависимость имеет постоянную стоимость памяти на общем сервере SQL Server. Вероятно, неплохо было бы открыть многие из них. – usr

+0

Прочитайте http://rusanu.com/2006/06/17/the-mysterious-notification/. Я точно знаю, как QN вёрст, и я говорю вам: продолжайте открывать много экземпляров одного и того же шаблона запроса с разными параметрами. Все в порядке. –

Смежные вопросы