[SQL SERVER]data cache in sql server 2016

SQL2016 In-Memory OLTP,現在可以讓我們很方便將資料存放至Memory table。

分散式Cache system我常見就是使用Redis,Redis效能雖然無庸置疑,

但我們必須付出額外維護和監控成本,如果你的系統架構都是MS導向,

那麼或許直接透過SQL2016 In-Memory OLTP來解決維護和Server成本問題,

且你可以使用熟悉TSQL來查詢現在有多少Cache類型、數量、資料..等,

無須額外使用Redis Desktop Manager並學習Redis知識和commands,

我看了MS分享In-Memory OLTP案例真讓人驚豔,

SQL2016 In-Memory OLTP 吞吐量1 200 000 batch requests/sec,

每秒可以有250,000 Requests~交易效能天殺的快

 

多台cache server,容易擴充且有HA。

Frmo Microsoft

 

透過SQL2016減少Server 數量,使用In-Memory OLTP為統一的Cache Database,

並透過Alwayson達到HA目的

Frmo Microsoft

Frmo Microsoft

 

實現上面核心架構相當簡單,從database角度來看基本上就是key/value概念,

你必須要先建立一個memory table,再透過native compilation SP和function進行InsertOrUpdate、

Delete(需搭配SQL Agent Job)、Select相關處理,

而我去年也把所有web server side的cache都搬入SQL2016 In-Memory table,

下面我簡單使用sessionID示範。

--create session table
CREATE TABLE dbo.[Sessions](
	SessionId nvarchar(88)  NOT NULL,
	Created datetime2 NOT NULL,
	Expires datetime2 NOT NULL,
	ItemSize bigint NOT NULL,
	Item varbinary(8000) NULL,
	CONSTRAINT [PK_Sessions_SessionId]
		PRIMARY KEY NONCLUSTERED HASH (SessionId) WITH (BUCKET_COUNT = 100)
) 
WITH (MEMORY_OPTIMIZED=ON, DURABILITY=SCHEMA_ONLY)—Alwayson下,必須改成 SCHEMA_AND_DATA,避免failover後資料遺失
GO

--確認sessionID筆數
CREATE FUNCTION [dbo].[uf_GetSessionCount_native](@SessionId nvarchar(88))   
RETURNS tinyint
WITH NATIVE_COMPILATION, SCHEMABINDING , EXECUTE AS OWNER
AS   
BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'English')  

    DECLARE @ReturnValue tinyint=0;  
    SET @ReturnValue = (
    select COUNT(1)
    from dbo.Sessions
    where SessionId=@SessionId)  
    RETURN (@ReturnValue);  

END  


--新增或更新session
CREATE PROCEDURE dbo.InsertOrUpdateStateItem(
	@SessionId nvarchar(88) NOT NULL,
	@Timeout int NOT NULL,	
	@ItemSize bigint NOT NULL,
	@Item varbinary(8000) NOT NULL
) WITH NATIVE_COMPILATION, SCHEMABINDING, EXECUTE AS OWNER
AS
BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'English')

	DECLARE @Now AS datetime2 NOT NULL = GETUTCDATE();
	DECLARE @Expires AS datetime2 NOT NULL = DATEADD(minute, @Timeout, @Now);
	DECLARE @new tinyint =0;

	select @new=dbo.uf_GetSessionCount_native(@SessionId);
	
	IF @new=1
	begin--update
	   UPDATE dbo.Sessions
		SET
			ItemSize = @ItemSize,
			Expires = @Expires,		
			Item = @Item
		WHERE SessionId = @SessionId
	end
	else if @new=0
	begin
	 insert dbo.Sessions(SessionId,Created,Expires,ItemSize,Item)
	 values(@SessionId,@Now,@Expires,@ItemSize,@Item)
	end	
END 
GO

--刪除過期session data
CREATE PROCEDURE dbo.DeleteOldSessions
WITH NATIVE_COMPILATION, SCHEMABINDING, EXECUTE AS OWNER
AS
BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'English')
   delete dbo.Sessions
   where Expires <GETUTCDATE()
END

--查詢session data
CREATE PROCEDURE dbo.GetSessionItem(@SessionId nvarchar(88) NOT NULL)
WITH NATIVE_COMPILATION, SCHEMABINDING, EXECUTE AS OWNER
AS
BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'English')
   select SessionId,Created,Expires,ItemSize,Item --explicit column
   from dbo.Sessions
   where SessionId =@SessionId
END

 

Controller我簡單隨便寫~XD

public class HomeController : Controller
    {
        private readonly string _connectionstring = ConfigurationManager.ConnectionStrings["dev"].ConnectionString;
        // GET: Home
        public ActionResult Index()
        {
            var userinfo = new UserInfo
            {
                Name = "rico",
                Age = 34,
                Dep = "dev",
                Email = "rico@moon.com",
                Role = "user"
            };
            string sql = @"dbo.InsertOrUpdateStateItem";
            var sessionid = Guid.NewGuid().ToString();
            var item = ObjectToByteArray(userinfo);
            using (IDbConnection connection = new SqlConnection(_connectionstring))
            {
                var result = connection.Execute(sql, new { SessionId = sessionid, Timeout = 30, ItemSize = item.LongLength, Item = item },
                      null, commandType: CommandType.StoredProcedure);
            }
            using (IDbConnection connection = new SqlConnection(_connectionstring))
            {
                var result = connection.Query<byte[]>("select Item from  dbo.Sessions where SessionId=@SessionId", new { SessionId = sessionid })
                    .FirstOrDefault();
                return View(ByteArrayToObject(result));
            }
        }

        byte[] ObjectToByteArray(object obj)
        {
            if (obj == null)
                return null;
            BinaryFormatter bf = new BinaryFormatter();
            using (MemoryStream ms = new MemoryStream())
            {
                bf.Serialize(ms, obj);
                ms.Position = 0;
                return ms.ToArray();
            }
        }

        UserInfo ByteArrayToObject(byte[] userinfoData)
        {
            using (MemoryStream ms = new MemoryStream(userinfoData))
            {
                BinaryFormatter bf = new BinaryFormatter();

                return (UserInfo)bf.Deserialize(ms);
            }
        }
    }

 

View也是青菜拉~哈

--設定Delete OldSessionData agent job(每10秒掃一次Sessions memory table)
USE [msdb]
GO

/****** Object:  Job [DeleteOldSessions]    Script Date: 2017/10/4 下午 04:41:40 ******/
BEGIN TRANSACTION
DECLARE @ReturnCode INT
SELECT @ReturnCode = 0
/****** Object:  JobCategory [[Uncategorized (Local)]]    Script Date: 2017/10/4 下午 04:41:40 ******/
IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1)
BEGIN
EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]'
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

END

DECLARE @jobId BINARY(16)
EXEC @ReturnCode =  msdb.dbo.sp_add_job @job_name=N'DeleteOldSessions', 
		@enabled=1, 
		@notify_level_eventlog=0, 
		@notify_level_email=0, 
		@notify_level_netsend=0, 
		@notify_level_page=0, 
		@delete_level=0, 
		@description=N'No description available.', 
		@category_name=N'[Uncategorized (Local)]', 
		@owner_login_name=N'RICONB\rico', @job_id = @jobId OUTPUT
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
/****** Object:  Step [Delete]    Script Date: 2017/10/4 下午 04:41:41 ******/
EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Delete', 
		@step_id=1, 
		@cmdexec_success_code=0, 
		@on_success_action=1, 
		@on_success_step_id=0, 
		@on_fail_action=2, 
		@on_fail_step_id=0, 
		@retry_attempts=0, 
		@retry_interval=0, 
		@os_run_priority=0, @subsystem=N'TSQL', 
		@command=N'exec dbo.DeleteOldSessions', 
		@database_name=N'mymemoryDB', 
		@flags=0
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'DeleteOldSessions', 
		@enabled=1, 
		@freq_type=4, 
		@freq_interval=1, 
		@freq_subday_type=2, 
		@freq_subday_interval=10, 
		@freq_relative_interval=0, 
		@freq_recurrence_factor=0, 
		@active_start_date=20171004, 
		@active_end_date=99991231, 
		@active_start_time=0, 
		@active_end_time=235959, 
		@schedule_uid=N'6ff25ed2-016c-4e83-b5b9-94c372351736'
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)'
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
COMMIT TRANSACTION
GOTO EndSave
QuitWithRollback:
    IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION
EndSave:

GO

 

接下來用我熟悉TSQL查詢這些Cache data

目前有4筆資料。

 

30分鐘過後,再次用熟悉的TSQL查詢cache data

過期資料自動會被agent job刪除,GC會自動回收並減少記憶體用量(只剩下3筆)。

 

參考

In-Memory OLTP in Azure SQL Database

SQL Server In-Memory OLTP as ASP.NET Session State Provider

New: ASP.NET Session State Provider for SQL Server In-Memory OLTP

Storing ASP.NET session outside webserver – SQL Server vs Redis vs Couchbase

Windows Server 2016 Hyper-V large-scale VM performance for in-memory transaction processing

ASP.NET Session State with SQL Server 2016

How bwin is using SQL Server 2016 In-Memory OLTP to achieve unprecedented performance and scale

Scalar User-Defined Functions for In-Memory OLTP