Friday, July 30, 2021

What Counts For a DBA: Patients

It’s been a while since I last wrote a new blog, and it has been a very long time since I last wrote a fresh new “What Counts For a DBA” entry. I celebrate this with a pun title.

The last technical blog I wrote was scheduled to be published on March 19, 2021 and was part of a group of blogs I was writing about SQL Server’s Graph features. This is leading to a book that I should have been really close to finishing at this point (I have not started yet!)

March 19th was also the day of a planned minor surgery. I was supposed to be back to working shape and continuing to learn new SQL Server features and writing more blogs a few weeks later, and pretty much back to normal in about 8 weeks. Instead, over the next month I spent less than a week at home because I was in a hospital bed due to multiple surgery complications. During those weeks in the hospital, I would listen to doctors try to figure out what was specifically wrong with me and I started remembering why I had thought about becoming a doctor when I was young. There is a problem, here are the symptoms, they are going to find the problem no matter how hard they had to look.

A certain aversion to the stuff you have to see, smell, and touch removed the medical profession from my list of possible career but not my desire to find and fix things. A long and winding path of choices led me to try out programming in college which eventually led me to stumble upon the career of a DBA/database developer. Never did I realize the job would be so similar to what a doctor does at times… (With the great exception that if something is gross in my workspace, it probably came off my lunch plate, not someone’s body…I did say probably.)

Every time a user comes to me with some malady: “Computer’s slow,” “I can’t save this account’s data,” etc., it is time to start acting like a doctor. Ask the patient where it hurts, which for SQL Server is to look at the Dynamic Management Views and other places like the Windows logs. Next, have the patient demonstrate their issue by executing the code. Like any good doctor, you don’t just give up and say, “that’s all I know to do with this customer’s data, tell them they should go elsewhere to get their business.” You need to persevere to a level far beyond your basic limits and find the problem. This is made so much easier since the Internet now has all the documentation you would ever need at your fingertips. Previously we had stacks of books like Gray’s Anatomy to dig through, some that came with the server, others we bought.

The hardest part of diagnosing your systems issue is knowing when you are out of your league. Sometimes you execute a bit of code like:

BARKUP DATABASE master;

Or you execute:

BACKUP DATABASE master;

Each statement can produce a plethora of errors (okay, BARKUP has far fewer errors possible, but still more than 1 error message could be output!), any of which you may not actually know the meaning of. What you do next is key. Do you patiently sort through the possibilities, hunting and searching, trying things, or do you immediately call for help?

If you can’t solve a problem easily, what comes next is most interesting. I often find myself working long hours hunting for a solution. While  I don’t advocate spending all your personal time working to try to solve a work problem, sometimes it does end up eating at me if I can’t solve an issue and I spend days and nights trying to find the solution. I have worked many nights trying to solve some little issue because I knew I could do it. Usually I was correct… usually.

Complicating matters, almost every single problem you encounter will be something that is essentially your fault (or a coworker/consultant’s fault) that has a definite solution, but sometimes it can be an actual bug in SQL Server. There are few bugs in SQL Server that you might encounter with rare frequency, but when you hit one, it can reduce your hair supply.

But giving up? Everyone needs to know their own limits. But I can tell you from experience, persevering and finding a solution for the health of your patient…human, animal, or computer…is worth every second.

The post What Counts For a DBA: Patients appeared first on Simple Talk.



from Simple Talk https://ift.tt/2VdHtqh
via

SQL Compilations/sec is not what you think it is

I was recently working in a SQL Server health check assessment, and the scenario I was investigating made me re-evaluate what I knew about performance counters SQL Compilations/sec, Cache Hit Ratio and the relation of those to Batch Requests/sec.

Consider the following scenario:

Perfmon counters showing high sql compliations/sec

As you can see, the number of SQL Compilations/Sec is very high. It’s important to step back and remember the general description and guideline for this counter and understand what I mean by “high”:

Official Description: “Number of SQL compilations per second. Indicates the number of times the compile code path is entered.”

A typical description: “The number of times that Transact-SQL compilations occur, per second (including recompiles). The lower the value the better. Each compilation uses CPU and other resources, so SQL Server will attempt to reuse cached execution plans wherever possible. Not having to recompile the stored procedure reduces the overhead on the server and speeds up overall performance.”

Guidelines:

  • Guideline from Red-Gate’s SQL Monitor tool: “In general, Compilations/sec should be less than 10% of the Batch requests/sec. High values often indicate excessive adhoc querying and should be as low as possible.”
  • Guideline from Brent Ozar: “In a transactional system, I expect to see 90% (or higher) query plan reuse – so Compilations/sec should be 10% (or less) of the Batch Request/sec measure.”

Consider the “10% of batch requests/sec guideline”. The counters show 21374 compilations/sec and 39693 batch requests/sec, (21374/39693) * 100 = 54%. Considering the 10% guideline, 54% looks like a very high number.

Another thing that caught my attention is the low value (69%) of “Plan Cache Hit ratio” (Ratio between cache hits and lookups.). The general description for this counter is: “how frequently a compiled plan is found in the plan cache (therefore avoiding the need to compile or a recompile)”. A good guideline is: “the closer this number is to 100%, the better.”

Note: There are other things that I could highlight like, plan cache counts is close to the default limit of 160,036 entries in a 64 bits system.

As always, it is a good practice to ask why. Why those guidelines? Why is evaluating those counters important? Why is plan cache reuse important?

SQL Server plan cache – Compilation and optimization phases

Query plan compilation and optimization are known to be CPU-intensive operations; therefore, SQL Server will attempt to reuse cached execution plans wherever possible. Not having to compile (or recompile) and optimize a statement reduces the overhead on the server and speeds up overall performance. This is the main reason to review the counters I mentioned before.

Notice that I mentioned “compilation” and “optimization.” Those are two different things and are part of the execution plan creation process. Although the cost of a compilation is not low, the optimization phase usually uses more CPU.

In environments with ad hoc intensive workloads, the analysis of performance counter numbers and CPU overhead cost may get tricky (more on that in the “plan cache for ad hoc queries session” of this article). Because of that, it is important to identify the query execution workload. The query plan cache reuse and query execution workload may be one of the following:

  • Ad hoc queries
  • Auto parameterized queries
  • Forced parameterized queries
  • Ad hoc with forced parameterized queries
  • Prepared queries using either sp_executesql or the prepare and execute method invoked through the data access API (ADO.NET, OLE DB, ODBC, JDBC and etc.)
  • Stored procedures or other compiled objects (triggers, TVFs, etc.)

One way to identify the workload is to look at the SQL compilations/sec counter and its relation to the batch requests/sec. In general, the higher the number of compilations/sec in relation to the batch requests/sec, the more likely the environment is experiencing an ad hoc workload.

Another quick option to check the workload is to look at sys.dm_exec_cached_plans DMV. For instance, shown in the following image (script based on Erin’s script), the number of ad hoc plans with only one use is very high. The ratio of how many single-use count plans compared to all cached plans is 80%. This is a good indication that this is an ad hoc workload. Also, notice that SQL is using only 415MB to store 156883 ad hoc plans in cache; that is a good indication that the instance probably has the “optimize for ad hoc workloads” server configuration option enabled.

Results of sys.dm_exec_cached_plans

Based on those numbers, the server received many ad hoc queries (an ad hoc workload).

Before I move on with the analysis, I’ll recap how SQL Server manages the plan cache for ad hoc queries, as this is important to understand the scenario.

Plan cache for ad hoc queries

As I wrote before on simple-talk, the chances that a query plan for an ad hoc query to be reused is very low because of the way SQL Server caches ad hoc plans.

In general, the cached plan for an ad hoc query will only be used only in the following conditions:

  • Subsequent query statement matches exactly (that includes the query and the filter values).
  • Query qualifies for simple parameterization (also referred to as auto-parameterization).
  • Query qualifies for forced parameterization, and it is enabled at the database or via plan guide using the PARAMETERIZATION FORCED query hint.

Take a look at a sample of all the mentioned conditions. To run the tests, I’m using the following script to populate the sample tables:

USE Northwind
GO
-- Run tests without ad hoc workloads
EXEC sys.sp_configure N'show advanced options','1';
GO
RECONFIGURE WITH OVERRIDE;
GO
EXEC sys.sp_configure N'optimize for ad hoc workloads', N'0'
GO
RECONFIGURE WITH OVERRIDE
GO
IF OBJECT_ID('OrdersBig') IS NOT NULL
  DROP TABLE OrdersBig
GO
SELECT TOP 500000
       IDENTITY(Int, 1,1) AS OrderID,
       ABS(CheckSUM(NEWID()) / 10000000) AS CustomerID,
       CONVERT(Date, GETDATE() - (CheckSUM(NEWID()) / 1000000)) AS OrderDate,
       ISNULL(ABS(CONVERT(Numeric(18,2), (CheckSUM(NEWID()) / 1000000.5))),0) AS Value,
       CONVERT(VarChar(250), NEWID()) AS Col1
  INTO OrdersBig
  FROM master.dbo.spt_values A
 CROSS JOIN master.dbo.spt_values B CROSS JOIN master.dbo.spt_values C CROSS JOIN master.dbo.spt_values D
GO
ALTER TABLE OrdersBig ADD CONSTRAINT xpk_OrdersBig PRIMARY KEY(OrderID)
GO
IF OBJECT_ID('CustomersBig') IS NOT NULL
  DROP TABLE CustomersBig
GO
SELECT TOP 500000
       IDENTITY(Int, 1,1) AS CustomerID,
       SubString(CONVERT(VarChar(250),NEWID()),1,20) AS CompanyName, 
       SubString(CONVERT(VarChar(250),NEWID()),1,20) AS ContactName, 
       CONVERT(VarChar(250), NEWID()) AS Col1, 
       CONVERT(VarChar(250), NEWID()) AS Col2
  INTO CustomersBig
  FROM master.dbo.spt_values A
 CROSS JOIN master.dbo.spt_values B CROSS JOIN master.dbo.spt_values C CROSS JOIN master.dbo.spt_values D
GO
ALTER TABLE CustomersBig ADD CONSTRAINT xpk_CustomersBig PRIMARY KEY(CustomerID)
GO
IF OBJECT_ID('ProductsBig') IS NOT NULL
  DROP TABLE ProductsBig
GO
SELECT TOP 500000 IDENTITY(Int, 1,1) AS ProductID, 
       SubString(CONVERT(VarChar(250),NEWID()),1,20) AS ProductName, 
       CONVERT(VarChar(250), NEWID()) AS Col1
  INTO ProductsBig
  FROM master.dbo.spt_values A
 CROSS JOIN master.dbo.spt_values B CROSS JOIN master.dbo.spt_values C CROSS JOIN master.dbo.spt_values D
GO
UPDATE ProductsBig SET ProductName = 'Produto qualquer'
WHERE ProductID = 1
GO
ALTER TABLE ProductsBig ADD CONSTRAINT xpk_ProductsBig PRIMARY KEY(ProductID)
GO
IF OBJECT_ID('Order_DetailsBig') IS NOT NULL
  DROP TABLE Order_DetailsBig
GO
SELECT OrdersBig.OrderID,
       ISNULL(CONVERT(Integer, CONVERT(Integer, ABS(CheckSUM(NEWID())) / 1000000)),0) AS ProductID,
       GetDate() -  ABS(CheckSUM(NEWID())) / 1000000 AS Shipped_Date,
       CONVERT(Integer, ABS(CheckSUM(NEWID())) / 1000000) AS Quantity
  INTO Order_DetailsBig
  FROM OrdersBig
GO
ALTER TABLE Order_DetailsBig ADD CONSTRAINT [xpk_Order_DetailsBig] PRIMARY KEY([OrderID], [ProductID])
GO
CREATE INDEX ixContactName ON CustomersBig(ContactName)
CREATE INDEX ixProductName ON ProductsBig(ProductName)
CREATE INDEX ixCustomerID ON OrdersBig(CustomerID) INCLUDE(Value)
CREATE INDEX ixProductID ON Order_DetailsBig(ProductID) INCLUDE(Quantity)
CREATE INDEX ixCol1OrderDate ON OrdersBig(Col1, OrderDate) INCLUDE(CustomerID, Value)
GO

Statement match

For example, execute the following four queries in the database.

-- Statement match
-- SET PARAMETERIZATION to SIMPLE 
ALTER DATABASE Northwind SET PARAMETERIZATION SIMPLE;
GO
DBCC FREEPROCCACHE()
GO
SELECT * FROM CustomersBig WHERE ContactName = 'Fabiano' OR CompanyName = 'A company'
GO
SELECT * FROM CustomersBig WHERE ContactName = 'Fabiano' OR CompanyName = 'A company'
GO
SELECT * /*important comment*/ FROM CustomersBig WHERE ContactName = 'Fabiano' OR CompanyName = 'A company'
GO
SELECT * FROM CustomersBig WHERE ContactName = 'Amorim' OR CompanyName = 'A company'
GO

The first and second queries will use the same plan, but the third and fourth will need to generate a new plan. Notice that the only difference between second and third queries is the comment which is enough to invalidate plan reuse.

Following are the details from the cache DMVs:

SELECT a.plan_handle, 
       a.usecounts,
       a.cacheobjtype,
       a.objtype,
       a.size_in_bytes,
       b.text,
       c.query_plan
  FROM sys.dm_exec_cached_plans a
 OUTER APPLY sys.dm_exec_sql_text (a.plan_handle) b
 OUTER APPLY sys.dm_exec_query_plan (a.plan_handle) c
 WHERE b."text" NOT LIKE '%sys.%'
   AND b."text" LIKE '%CustomersBig%'
 ORDER BY a.usecounts DESC
GO

Results of plan cache query

As you can see, to take advantage of the reuse of ad hoc query plans, you need to make sure that the queries are identical, character for character. If one query has a new line or an extra space that another one doesn’t have, they will not be treated as the same. If one contains a comment that the other doesn’t have, they will not be identical. If one uses a different case for either identifiers or keywords, even in a DB with a case-insensitive collation, queries will not be considered the same.

Since SQL considered those queries to be different, each call triggered a query plan compilation and optimization.

Simple parameterization

SQL Server may use ‘simple parameterization’ to turn the literal values into parameters and increase the ability to match SQL statements for different literal values. When this happens, subsequent queries that follow the same basic parameterized query can use the same plan. For example:

-- Auto-param
-- SET PARAMETERIZATION to SIMPLE 
ALTER DATABASE Northwind SET PARAMETERIZATION SIMPLE;
GO
DBCC FREEPROCCACHE()
GO
SELECT * FROM OrdersBig WHERE OrderID = 1
GO
SELECT * FROM OrdersBig WHERE OrderID = 2
GO
SELECT * FROM OrdersBig WHERE OrderID = 3
GO

Internally, SQL Server parameterizes the queries as follows:

SELECT * FROM [OrdersBig] WHERE [OrderID]=@1

You can also see the parameterized query in the actual execution plan; notice that the @1 variable is used instead of the literal value:

Execution plan showing parameter

Following, you can see the details from the cache DMVs:

SELECT a.plan_handle, 
       a.usecounts,
       a.cacheobjtype,
       a.objtype,
       b.text,
       c.query_plan
  FROM sys.dm_exec_cached_plans a
 OUTER APPLY sys.dm_exec_sql_text (a.plan_handle) b
 OUTER APPLY sys.dm_exec_query_plan (a.plan_handle) c
 WHERE b."text" NOT LIKE '%sys.%'
   AND b."text" LIKE '%OrdersBig%'
 ORDER BY a.usecounts DESC
GO

Cached plans

Notice that the three individual queries with their distinct constants do get cached as ad hoc queries. However, these are only considered shell queries and don’t contain the complete execution plan, only a pointer to the full plan in the corresponding prepared plan.

Let me add more comments about those shell queries because they’re the reason I’m writing this article:

  • A shell query compilation doesn’t result in an optimization since only the prepared/parameterized query gets optimized. This is good, but the side effect of this is that even those shell queries that don’t get reused will often be cached and use memory. To minimize the impact of those queries, SQL caches them as zero-cost queries. That way, under a memory pressure condition, these would be among the first entries that would be freed from cache. You could look at sys.dm_os_memory_cache_entries to check the cost of each memory object entry.
  • The shell queries are only cached to make it easier to find the prepared version of the query if the exact same query with the same constant is executed again later. It will only be useful if you actually re-run the same ad hoc query again, which, for most scenarios, is very unlikely (at least, considering the majority of the queries). Microsoft probably have a reason why they’re doing it, but I’ve noticed that most environments are hitting the plan cache limit due to the high number of ad hoc queries, and forced parameterization can do nothing to help with it. In the article “Useful Queries on DMV’s to understand Plan Cache Behavior” they mentioned that “In some workloads, there is reuse of adhoc queries with the exact same parameter values. In such cases caching of the shell query proves gives better throughput.”
  • In my opinion, storing those garbage ad hoc queries in a limited cache space is not a good idea. I wish I had an option (trace flag, anyone?) to change this behavior and not cache the ad hoc queries when a prepared version of the query exists.

Take a look at the XML for the second row (the one with the ad hoc plan with OrderID = 3):

<ShowPlanXML xmlns="http://schemas.microsoft.com/sqlserver/2004/07/showplan" Version="1.539" Build="15.0.4138.2">
  <BatchSequence>
    <Batch>
      <Statements>
        <StmtSimple StatementText="SELECT * FROM OrdersBig WHERE OrderID = 3" StatementId="1" StatementCompId="1" StatementType="SELECT" RetrievedFromCache="true" ParameterizedPlanHandle="0x0600050024F5793700E98CD06C02000001000000000000000000000000000000000000000000000000000000" ParameterizedText="(@1 tinyint)SELECT * FROM [OrdersBig] WHERE [OrderID]=@1" />
      </Statements>
    </Batch>
  </BatchSequence>
</ShowPlanXML>

Notice that there is an attribute ParameterizedPlanHandle with value “0x0600050024F5793700E98CD06C02000001000000000000000000000000000000000000000000000000000000” which is the pointer to the prepared query that has the full execution plan.

By default, SQL Server simple-parameterizes a relatively small class of queries. The engine is very conservative about deciding when to use simple parameterization for a query. It will only parameterize queries if the prepared query plan is considered to be safe.

Forced parameterization

You can enable forced parameterization at the DB level to enable parameterization for all queries in the database, subject to certain limitations (for a list of limitations and exceptions, check the BOL).

Once forced param is enabled, the result will be pretty much the same as simple-param, but for all queries.

For example, consider the following queries that would not be simple-parameterized but are parameterized under forced param:

-- Forced-param
-- SET PARAMETERIZATION to FORCED 
ALTER DATABASE Northwind SET PARAMETERIZATION FORCED;
GO
DBCC FREEPROCCACHE()
GO
SELECT * FROM OrdersBig WHERE OrderID = 1 OR Value < 0.2
GO
SELECT * FROM OrdersBig WHERE OrderID = 2 OR Value < 0.4
GO
SELECT * FROM OrdersBig WHERE OrderID = 3 OR Value < 0.7
GO

Following, you can see the details from the cache DMVs:

Stored procedures and sp_executesql

It is a best practice to use stored procedures or rely on data access methods parameterization using param markers to increase the reuse of execution plans. For example, consider the same query used with forced param. Once the developer identifies all the parameters in the query, they can use parameter markers (question marks) to replace a constant in an SQL statement and are bound to a program variable. Using parameters at the application, you would see the following calls in the SQL Server:

-- sp_executesql
-- SET PARAMETERIZATION to SIMPLE
ALTER DATABASE Northwind SET PARAMETERIZATION SIMPLE;
GO
DBCC FREEPROCCACHE()
GO
EXEC sp_executesql N'SELECT * FROM OrdersBig WHERE OrderID = @OrderID OR Value < @Value', 
                   N'@OrderID Int, @Value NUMERIC(18,2)',
                   @OrderID =1,
                   @Value = 0.2
GO
EXEC sp_executesql N'SELECT * FROM OrdersBig WHERE OrderID = @OrderID OR Value < @Value', 
                   N'@OrderID Int, @Value NUMERIC(18,2)',
                   @OrderID = 2,
                   @Value = 0.4
GO
EXEC sp_executesql N'SELECT * FROM OrdersBig WHERE OrderID = @OrderID OR Value < @Value', 
                   N'@OrderID Int, @Value NUMERIC(18,2)',
                   @OrderID = 3,
                   @Value = 0.7
GO

Following, you can see the details from the cache DMVs:

When using sp_executesql, there are no entries for the ad hoc shell query (unparameterized) queries. Not having the ad hoc shell queries in the plan cache is a key factor for the analysis I was doing, as 80% of the plan cache was filled with those shell queries.

Note: It is worthy of adding a note in favor of parameterization. Using parameters to hold values that end users type is more secure than concatenating the values into a string that is then executed by using either a data access API method, the EXECUTE statement, or the sp_executesql stored procedure. SQL injection, anyone?

Back to the scenario analysis

Now that you understand how SQL manages plan cache for ad hoc queries, it’s time to get back to the analysis.

Once I looked at the batch request/sec and compilations/sec numbers, I started to question myself: How can I have such a high number of compilations/sec if most requests use forced parameterization? (notice that forced param/sec counter is pretty much the same as compilations/sec). If an ad hoc query execution is parameterized (via forced param), how could I still have a compilation? Shouldn’t they use the “prepared” plan and count as a cache hit (found the plan in cache)?

Remember, on both simple-param and forced-param, SQL Server also adds the ad hoc shell query in the plan cache. When it happens, since the plan for the ad hoc query is not in cache, it counts as a cache miss (couldn’t find query plan in cache). This will decrease the plan cache hit ratio and increases compilations/sec. However, there is an important thing to consider: those compilations/sec are not triggering an optimization, so the CPU cost of the compilation is not so bad. Since it is not triggering the optimization, it may use less CPU.

In other words, considering CPU usage, a high number of compilation/sec per batch requests/sec may not necessarily be a problem. This is still definitely something of concern if you consider the internal/local memory pressure it may cause. As a result of having many ad hoc plans in cache, you may have the following problems:

  1. If the number of ad hoc shell entries gets too big, you’ll reach a point (there is no more room left in plan cache) where you hit the limit of entries in the plan cache memory table. This will make SQL run the eviction policy algorithm to determines which plans to remove from cache.
  2. When the plan cache reaches its entry limit, plans with low cost must be evicted in order to insert new plans. Suppose there are many concurrent users/sessions trying to insert or remove rows in the same hash table (in the case of ad hoc queries, I’m talking about the SQL Plans internal area). In that case, there may be spinlock (SOS_CACHESTORE and/or SOS_CACHESTORE_CLOCK) contention which can cause high CPU usage.
  3. Consider a scenario with the same ad hoc query being called hundreds of times per second. SQL will cache the ad hoc shell query for each unique (individual calls using different constant values) call, even though there may be only one cached prepared plan available and being reused. In such scenarios, the CMEMTHREAD wait may start to pop up, indicating you have contention on the memory object from which cache descriptors are allocated. Even though Microsoft already tried to optimize the code to avoid this, you may still see those.
  4. If optimize for ad hoc workloads is not enabled, you’ll probably end up with a cache bloat problem.

Considering that, I decided to check the internal memory pressure condition. I started by looking at the wait CMEMTHREAD, and it is indeed there.

Memory pressure

I’ve also looked at DMV sys.dm_os_memory_cache_clock_hands, This DMV has information about how many clock rounds have been made for each cache store. The query below returns information about cache stores with most entries removed in the last round. Notice that internal (internal and external clock hand distinguishes internal and external memory pressure respectively) clock hand for CACHESTORE_SQLCP indicates the number of entries (plans) removed in the last round was 161098 (pretty much everything).

SELECT mcch.name,
       mcch.[type],
       mcch.clock_hand,
       mcch.clock_status,
       SUM(mcch.rounds_count) AS rounds_count,
       SUM(mcch.removed_all_rounds_count) AS cache_entries_removed_all_rounds,
       SUM(mcch.removed_last_round_count) AS cache_entries_removed_last_round,
       SUM(mcch.updated_last_round_count) AS cache_entries_updated_last_round,
       SUM(mcc.pages_kb) AS cache_pages_kb,
       SUM(mcc.pages_in_use_kb) AS cache_pages_in_use_kb,
       SUM(mcc.entries_count) AS cache_entries_count,
       SUM(mcc.entries_in_use_count) AS cache_entries_in_use_count,
       CASE
           WHEN mcch.last_tick_time
                BETWEEN -2147483648 AND 2147483647
                AND si.ms_ticks
                BETWEEN -2147483648 AND 2147483647 THEN
               DATEADD(ms, mcch.last_tick_time - si.ms_ticks, GETDATE())
           WHEN mcch.last_tick_time / 1000
                BETWEEN -2147483648 AND 2147483647
                AND si.ms_ticks / 1000
                BETWEEN -2147483648 AND 2147483647 THEN
               DATEADD(s, (mcch.last_tick_time / 1000) - (si.ms_ticks / 1000), GETDATE())
           ELSE
               NULL
       END AS last_clock_hand_move
FROM sys.dm_os_memory_cache_counters mcc (NOLOCK)
    INNER JOIN sys.dm_os_memory_cache_clock_hands mcch (NOLOCK)
        ON mcc.cache_address = mcch.cache_address
    CROSS JOIN sys.dm_os_sys_info si (NOLOCK)
WHERE mcch.rounds_count > 0
GROUP BY mcch.name,
         mcch.[type],
         mcch.clock_hand,
         mcch.clock_status,
         mcc.pages_kb,
         mcc.pages_in_use_kb,
         mcch.last_tick_time,
         si.ms_ticks,
         mcc.entries_count,
         mcc.entries_in_use_count
ORDER BY SUM(mcch.removed_all_rounds_count) DESC,
         mcch.[type];

I also noticed that this was removing avg of 161k entries every 5 seconds.

Luckily, the number of CPUs (96) available in the server was good enough to keep up with the workload and avoid CPU pressure, but that only happened because it has forced parameterization enabled in all DBs in the instance. This was helping to avoid the full optimization of all the ad hoc queries, which I’m sure would make the CPU go to 100% in just a few seconds if it wasn’t the case. Forced parameterization does fix the issue of optimizing the query plan for each ad-hoc query execution, but it does not fix the issue of compiling the plan and bloating the cache with garbage plans which may cause internal memory pressure and consequently the plan cache to be cleaned and the spinlock contention.

The lesson I learned was, from now on, I’ll look not only at batch requests/sec, compilations/sec and plan cache hit ratio, but also in another important counter which is the “Workload Group Stats: Query optimizations/sec”. When considering this counter, it would be easier to identify those cases where ad hoc queries are causing a high number of compilations but not necessarily triggering optimization. For instance, the following is the same image as before, but now with the info about the query optimizations/sec:

Perfmon counter report

Notice that out of 20905 compilations per second, there were only 247 query optimizations per second—a much better picture of what is going on with the server. The general guideline of “Compilations/sec should be less than 10% of the Batch Request/sec” is still valid as a good practice, but, as you know, a general guideline doesn’t apply for all scenarios.

Time for some tests

Here are some tests to simulate the same scenario I presented before to see it’s possible to identify the problems and analyze the alternatives and the benefits it may provide.

To run the tests, I created the following PowerShell script to run an ad hoc query 50000 times.

Clear-Host
$ScriptBlock = {
    $conn = New-Object System.Data.SqlClient.SqlConnection("Server=vm3;Database=Northwind;User ID=sa;Password=-------;")
    $conn.Open()
    $i = 1
    while ($i -le 50000){
        try {
            $sqlCmd = New-Object System.Data.SqlClient.SqlCommand
            $sqlCmd.Connection = $conn
            [string]$Guid = New-Guid            
            $query = "SELECT *, (Select 1) AS Num FROM OrdersBig o 
                        WHERE o.Col1 = '$Guid'"
            $sqlCmd.CommandText = $query
            $reader = $sqlCmd.ExecuteReader()
            $reader.Close()
            $i = $i + 1
        } 
        catch {
            $ErrorMessage = $_.Exception.Message
            $vGetDate = Get-Date -Format G
            Write-Output "$vGetDate : Connection Failed "$ErrorMessage"... retrying..."
        }
    }
    $conn.Dispose()   
}
$current = Get-Date
Invoke-Command -ScriptBlock $ScriptBlock
$end= Get-Date
$diff= New-TimeSpan -Start $current -End $end
Write-Output "Time to run the script: $diff"
<#
exec xp_cmdshell 'powershell -NoLogo -NoProfile "C:\Temp\Test.ps1"'
#>

Then, I’m using SQLQueryStress to call the script with 100 threads using xp_cmdshell. (I know there are easier ways to do it via PS, but that’s good enough for me 😊)

Before I run the tests, I’m also enabling optimize for ad hoc workloads to avoid memory pressure due to the size of the plan caches using the following code:

-- Run tests with ad hoc workloads
EXEC sys.sp_configure N'optimize for ad hoc workloads', N'1'
GO
RECONFIGURE WITH OVERRIDE
GO

Test 1: Ad hoc with simple param triggering compilation and optimization

For the first execution, I tried the script with simple parameterization using 100 concurrent threads. Since the query is a bit complex, it won’t be auto-parameterized, which means it will trigger a compilation and an optimization for each call. The script took 12 minutes and 16 seconds to run, and while it was running, I saw the following counter numbers:

Perfmon optimizations/sec

As per the image above, I was able to run an average of 7597 batch requests per second. After a few seconds, I hit the limit of entries in the plan cache. Also, as expected, each ad hoc query execution is compiled and optimized. Since those operations are CPU intensive, with only 7597 batch requests/sec of a very lightweight (query doesn’t return anything and is very fast) query, it is using almost 100% of CPU in my 96 cores VM. That’s a lot of CPU to do nothing other than creating execution plans.

CPU Utlization

Test 2: Ad hoc with forced param triggering compilation and only 1 optimization

For the second execution, I tried the script with forced parameterization and using the same 100 concurrent threads as before. This will trigger optimization only for the first call of the query, and all subsequent calls will reuse the prepared plan. The script took only 58 seconds to run, and while it was running, I saw the following counter numbers:

Results with parameterization

As per the image above, I was able to run an average of 94 thousand batch requests per second, and as expected, the compilations/sec counter is showing the same 94 thousand values as each ad hoc query counts as a compilation, although there were no optimizations. Because there was no extra CPU cost related to the optimizations for each call, I was able to run a lot more requests per second.

Test 3: Parameterized queries in the application

For the third execution, I tried the script with a query parameterized from the application. Again, I used the same 100 concurrent threads as before.

To parameterize it from the PS script, I’ve changed the following part of the code:

...
[string]$Guid = New-Guid
$Col1Param = New-Object System.Data.SqlClient.SqlParameter("@Col1",[Data.SQLDBType]::varchar, 250)
$Col1Param.Value = $Guid
$sqlCmd.Parameters.Add($Col1Param) | Out-Null
$query = "SELECT * FROM OrdersBig o 
            INNER JOIN CustomersBig c ON o.CustomerID = c.CustomerID 
            INNER JOIN Order_DetailsBig od ON o.OrderID = od.OrderID 
            INNER JOIN ProductsBig p ON od.ProductID = p.ProductID 
            WHERE o.Col1 = @Col1"
$sqlCmd.CommandText = $query 
...

Now, the script took only 51 seconds to run, and while it was running, I saw the following counter numbers:

Perfmon counters for parameterization

This is 106469 batch requests per second with CPU usage averaging at 90%. At this time, I thought, well, CPU at 90%, I think I can add a few more threads to see how it goes. Here is the result of the execution using 150 threads:

Result after increasing batches

125428 batch requests/sec, that’s a lot of requests 😊.

This will work under simple or forced parameterization, so, it is a good way of guarantee a good parameterized behavior.

To summarize the results:

Following, you can see a table with the summarized results. Using parameterized queries from the application, I was able to run 125 thousand batch requests per second, which is impressive.

Scenario

Parameterization

Threads

Avg of batch requests/sec

Time to run all requests

Test 1

Simple with auto param

100

7597

12 minutes and 16 seconds

Test 2

Forced

100

94988

58 seconds

Test 3

sp_executesql

100

106469

51 seconds

Test 3

sp_executesql

150

125428

61 seconds

SQL compilations/sec

Parameterization plays a very important role in the performance of a system. Although simple parameterization, forced parameterization, and optimize for ad hoc workload features can help to minimize the lack of parameterization, it is a good practice to parameterize queries at the application side or use stored procedures. Using stored procedures is also good because it usually avoids issues with implicit conversions due to wrong parameter data types (although even on SPs users can always use wrong datatypes if variable type is different than table column). This will reduce memory consumption, reduce memory pressure, and reduce CPU usage by saving the compilation and optimization time of each query.

 

If you liked this article, you might also like SQL Server performance monitor data: Introduction and usage

 

The post SQL Compilations/sec is not what you think it is appeared first on Simple Talk.



from Simple Talk https://ift.tt/378DSwq
via

Thursday, July 29, 2021

Appreciating tiny victories

No one is good at something right when they start. When starting something new, it’s normal to feel terrified or have “imposter syndrome.” You’ll constantly be rubbing shoulders with people that are more experienced, more talented, or seemingly more confident. 

It takes a long, long time to get good at something. Of course, there are hyper-talented freaks of nature: Adele, Lebron James, the dude from Coldplay. Even in the database world, people like Bob Ward or Kendra Little seem to have just been born with tech talent. They have an undeniable ability that will rise to the top no matter what.

Everyone else is a regular person.

All regular people have raw talent within — untapped gold. That talent takes time to discover. Once it is discovered, it must be mined. A regular person’s talent takes years to develop and refine.

I sucked at guitar when I started. I couldn’t sing for anything. But after 100 shows, my guitar work and voice started to be presentable. After 400 shows, my performance was decent. After 1000 shows, I had what I needed to make a living at it. It’s all a long burn. I’ll never be Eddie Van Halen, but that’s ok with me.

What about working with data? How do the experts seem to know all the answers? It’s because they have made the same mistakes and overcome the same issues you have. They figure it out and share what they have learned with the rest of us.  

We can’t compare ourselves to the Adeles, Lebrons, or Coldplay dudes of the world. Unfollow them on Instagram or Twitter and focus on mining your own talent. 🙂 You can still learn a ton from the Kendras and Bobs, but don’t kick yourself for not being them.

Mining talent takes time. It’s all about the long slow burn, so find something you like doing because you’ll need to do it for a while to get good. 

As that talent is mined, it slowly begins to garner “success” (however you define success), gratification, progress, or any combo of the three. Eventually, you can be the person others look to for answers.

If you are looking for “overnight success,” you will be disappointed. If you appreciate “tiny victories,” you are making progress and likely feeling gratified. 

It can be hard to spot the “tiny victories” along the way, but they are the motivational currency we need to keep going. Nonetheless, a victory is progress, no matter how big or small. Appreciating tiny victories is the fuel that keeps us trucking thru the long slow burn of mining our talents. 

Talent is mined. Tiny victories accumulate. Consistency is rewarded. Life is long, and we don’t need to put pressure on ourselves to “make it” by tomorrow, next month, or next week. So, if you’re itching to pursue something new, do it because if you have a good head on your shoulders — odds are you’ll land on your feet. 

Commentary Competition

Enjoyed the topic? Have a relevant anecdote? Disagree with the author? Leave your two cents on this post in the comments below, and our favourite response will win a $50 Amazon gift card. The competition closes two weeks from the date of publication, and the winner will be announced in the next Simple Talk newsletter.

The post Appreciating tiny victories appeared first on Simple Talk.



from Simple Talk https://ift.tt/37bFw04
via

Monday, July 26, 2021

Log Analytics and Azure Data Studio: New Extension

You need to dig into old blogs I wrote before to fully understand how interesting this new extension is.

On Saving Money with Log Analytics I mentioned how important log analytics is for azure clouds. This deserves more in depth analysis, which probably will be broken down in blog articles.

On Connecting to Log Analytics using Azure Data Studio and KQL  I introduced a solution to make Log Analytic queries in Python notebooks using Azure Data Studio. Comparing to the new feature I’m about to show this will seems like something from stone age.

On Using Power BI to Query Log Analytics  I made demonstrations with Power BI, bringing log analysis to another level.

The news: A new extension is in preview to allow connecting Azure Data Studio to Log Analytics as if you were connecting to a SQL database.

The extension is in preview, so you need to install it on Azure Data Studio Insiders version, which brings preview features in advance for us. If you are lost in space and don’t have the Insiders version installed yet, you can get it here 

Once you installed Azure Data Studio Insiders, let’s follow the steps you need to start querying Azure Log Analytics.

Install the Extension

This is easy and with no mystery: Use the extensions tab, look for Azure Monitor Log extension and install it.

Create a Connection

The regular Create Connection window has one additional option, Azure Monitor Logs. We will need to fill some specific log analytics details:

Connection Type: Azure Monitor Logs
Workspace Id: You can find this information on your Azure portal, on your Log Analytics workspace.

 

Authentication: You will need to authenticate against the Active Directory where your Log Analytics is. You will need to provide an account and Azure AD Tenant and the authentication will be made using this account
Database: There is only one option. It will only be filled after you provide the authentication
Server Group: This is an ADS detail about how you organize your connections. You can create a group for all your log analytics connections.
Name: the name of this connection on the UI

 

Look Around

Take a look around and check the result of your connection.

  • In the Connections panel you are able to navigate the tables inside log analytics down to the level of fields. Only a few tables are shared across many services, while others are specific to one azure service.

  • Right click the log analytics connection in the Connections panel and select Manage. The tab that will open will show you the tables once again, but here you have the option to create a new query or a new notebook.

Creating a new Notebook

Once you created a new Notebook, it will be connected to your Log Analytics. The kernel will be Log Analytics and not Kusto. Yes, I know Kusto is the Log Analytics language, but Azure Data Explorer already uses Kusto as Kernel. The kernel needs to be Log Analytics.

The notebook provides intellisense while you type your query, but doesn’t provide coloring options for the statements yet, the entire query is black, a boring black.

 

As we would expect from an ADS notebook, we can create graphics from the query results. For example, this is a graphic of weekly access on a website, by day of week:

 

The graphics in a notebook are not the same as a Log Analytics workbook. In order to achieve this line graphic I had to include a PIVOT function on the query, otherwise the graphic would not show this data, while on the workbook we could configure for this data.

Without the PIVOT function, this would be the result of the query:

 

Using the EVALUATE statement and the PIVOT function, we turn the content of the column Day into columns, allowing us to create the line graphic above:

 

Small Pending Problems

  • The Azure Connection is lost from time to time. We need to refresh the connection. On the notebook, we can use the Change Connection for that. On the Connections panel, we can disconnect and connect again.

  • The code doesn’t use any different color for the statements
  • The graphic features on a notebook may be more difficult to handle than on log analytics workbook. However, I don’t think this is really an issue, it’s more about different usages of the tools.

Conclusion

Each day we get new tools to access Log Analytics, increasing the importance of this great tool for Azure Clouds.

The post Log Analytics and Azure Data Studio: New Extension appeared first on Simple Talk.



from Simple Talk https://ift.tt/3eV0ODO
via

Automating EMR step submission from AWS Lambda

What is EMR?

Elastic MapReduce is an Amazon web service tool for big data processing and analysis. The MapReduce is a framework that allows programmers to process large scale data across distributed processors in parallel.

Amazon EMR processes big data across a Hadoop cluster of virtual servers on Amazon Elastic Compute Cloud (EC2) and Amazon Simple Storage Service (S3). The elastic in EMR’s name refers to its dynamic resizing ability, which allows it to ramp up or reduce resource use depending on the demand at any given time.

 

What is AWS Lambda?

Lambda is a compute service that lets you run code without provisioning or managing servers. Lambda runs your code on a high-availability compute infrastructure and performs all of the administration of the compute resources, including server and operating system maintenance, capacity provisioning and automatic scaling, code monitoring and logging. With Lambda, you can run code for virtually any type of application or backend service. Lambda runs the code in response to the events.

 

Automating EMR step execution from Lambda

As discussed lambda executes the code in response to the events. The events can be S3 based, SQS or SNS.

The below java example shows how to automate an EMR step via AWS Lambda

 

/* Get AWS Credentials*/

AmazonElasticMapReduce emr = AmazonElasticMapReduceClientBuilder.standard()
.withCredentials(new DefaultAWSCredentialsProviderChain()).withRegion(Regions.US_EAST_1).build();

/* Get the list of clusters which are running or either in waiting submit the step to your required lambda*/


ArrayList<String> ClusterStatus = new ArrayList<String>();
ClusterStatus.add("RUNNING");
ClusterStatus.add("WAITING");
ListClustersRequest lcreq = new ListClustersRequest().withClusterStates(ClusterStatus);
ListClustersResult lcres = emr.listClusters(lcreq);
for (ClusterSummary csummary : lcres.getClusters()) {
if (csummary.getName().equalsIgnoreCase(System.getenv("EMR_NAME"))) {

emrJobFlowId = csummary.getId();
}
}

if(!emrJobFlowId.isEmpty()) {

List<StepConfig> stepConfigs = new ArrayList<StepConfig>();
AddJobFlowStepsRequest req = new AddJobFlowStepsRequest();
req.withJobFlowId(emrJobFlowId);

// Run a custom jar file as a step
HadoopJarStepConfig hadoopConfig1 = new HadoopJarStepConfig().withJar("command-runner.jar").withArgs(
"spark-submit", "--deploy-mode", "client", "--class", System.getenv("EMR_CLASS_NAME"),
System.getenv("EMR_JAR_LOCATION")

 

Once the above lambda executes, a step is automatically submitted to the EMR and the EMR completes the job execution.

 

The post Automating EMR step submission from AWS Lambda appeared first on Simple Talk.



from Simple Talk https://ift.tt/3iPw1cn
via

Friday, July 23, 2021

Improving performance with instant file initialization

As a database administrator, one of your jobs is to make sure each SQL Server instance you manage is set up in such a way as to meet security requirements and is configured to optimize performance. There are lots of different aspects of securing and tuning performance for your SQL Server environment. This article discusses how instant file initialization can be configured to improve the performance of allocating disk space to a database when it is created, expanded, or restored.

What is instant file initialization?

Instant file initialization is a SQL Server setup option that keeps SQL Server from writing binary zeroes to data files when a database is first created, expanded, or restored. By avoiding the writing of binary zeroes, there is a lower performance impact when disk space is allocated for several database operations.

By default, when SQL Server creates a database, increases the size of a database, or restores a database, it needs to initialize the disk space prior to allocating the space. This initialization process writes binary zeroes (“0”) across all the bits and bytes of space being allocated. By writing binary zeroes across the disk space, the SQL Server engine makes sure that data previously stored in the unused disk space is completely overwritten before the disk space is allocated to a database.

Instant file initialization has been around since SQL Server 2005. When instant file initialization is turned on, the data files are not initialized with zeros. The transaction log always requires binary zeroes to be written when space is allocated to the log file. This is required so the database engine can maintain parity bits in each 512 byte disk sector.

Why the transaction log always needs to be initialized

Since the log file is circular, the database engine flip-flops the parity bits between 64 and 128 each time it wraps around. By changing the value of the party bits each time SQL Server wraps around, the database engine can determine which sectors are associated with the parity bit’s current value. When the log file sectors are initialized, they contain zeroes for the parity bits, which is an invalid value. The parity bit values are used during recovery to determine which sectors need to be used during the restore process, based on the current parity bits value. If SQL Server didn’t initialize the log file, then potentially, the file might have some residual data that looks like a valid parity bit value. More information about this can be found in this post by Paul Randal.

Is instant file initialization turned on?

When instant file initialization is turned on, the account running SQL Server will have been granted “SEManageVolumePermissions”. There are a couple of different options to determine if an instance has been set up to performed instant file initialization.

One method is to review the SQL Server log. When SQL Server starts up, it writes an informational message in the log file to indicate whether instant file initialization is enabled or disabled.

When instant file initialization is enabled, this informational message can be found in the log: Database Instant File Initialization: enabled. For security and performance considerations see the topic ‘Database Instant File Initialization’ in SQL Server Books Online. This is an informational message only. No user action is required.”

When instant file instant file initialization is disabled, this message will be displayed in the log: “Database Instant File Initialization: disabled. For security and performance considerations see the topic ‘Database Instant File Initialization’ in SQL Server Books Online. This is an informational message only. No user action is required.”

Another method to determine if instant file initialization is turned on is to look at the instant properties under the “Advanced” tab with the SQL Server Configuration Manager tool. Figure 1 shows the “Advanced” properties for one of my instances.

Image from configuration manager showing instant file initialization

Figure 1: Advanced tab configuration properties for instant MSSQLSERVER01

The “Instant File Initialization” property shows that the instance is set up for instant file initialization because the property is set to “Yes”. Note that only the SQL Server 2019 version of Configuration Manager allows you to see this property.

You can also use TSQL to determine if instant file initialization is enabled or disabled by using the “sys.dm_server_services” dynamic management view (DMV). The script in Listing 1 uses this DMV to show whether or not instant file initialization is turned on for an instance.

Listing 1: Is instant file initialization enabled?

SELECT servicename, instant_file_initialization_enabled 
FROM sys.dm_server_services
WHERE servicename like 'SQL Server (%';

When running the script in Listing 1 on my MSSQLSERVER01 instance, the information in Report 1 is displayed.

Report 1: Output when running Listing 1

Turning on instant file initialization when installing SQL ServerThe column “instant_file_initialization_enabled” is set to “Y” for the MSSQLSERVER01 service. This means the MSSQLSERVER01 instance has instant file initialization enabled. If that column were set to “N”, then instant file initialization would be disabled.

In the old days, prior to SQL Server 2016, you had to turn on instant file initialization manually. Starting with SQL Server 2016, turning on Instant File Initialization can be performed as part of the installation process.

If you install SQL Server using the installation wizard, instant file initialization can easily be turned on. Just check “Grant Perform Volume Maintenance Task privilege to SQL Server Database Engine Service” on the Server Configuration window of the installation wizard, as shown in Figure 2.

Image showing how to enable instant file initialization during SQL Server install

Figure 2: Server Configuration

You can also turn on instant file initialization when installing SQL Server from the command prompt. When running the setup.exe to install SQL Server, you just need to provide the /SQLSRVINSTANTFILEINT argument to enable instant file initialization.

Enabling instant file initialization after SQL Server is installed

There are a number of different ways to turn on instant file initialization after SQL Server has been installed. The first method to enable instant file initialization works for all versions of SQL Server that support it. Use the following steps to enable it:

  1. Log on to your SQL Server with an account that is a member of the local Windows Administrator group.
  2. Open the Local Security Policy application by running secpol.msc from a command prompt.
  3. In the left pane that is displayed when the Local Security Policy application starts running, click the “Local Policies” item.
  4. In the right pane double-click on the “User Rights Assignment”.
  5. Find the “Perform volume maintenance tasks” item in the right pane, and double-click to open the settings.
  6. Click the “Add User or Group” button in the “Local Security Setting” tab on the “Perform volume maintenance tasks Properties” screen
  7. Add the account name running SQL Server to the dialog box, where the green arrow points to in Figure 3 and click OK.
    Image showing how to enable instant file initialization through policy

Figure 3: Set up account so it can perform volume maintenance

  1. Click OK to accept the properties and close the “Local Security Policy” dialog box.
  2. Restart the SQL Server.

Starting with SQL Server 2019, you can use SQL Server 2019 Configuration manager to turn on instant file initialization. To do this, bring up SQL Server 2019 Configuration Manager, open the “advanced” tab, and find the “Instant File Initialization” property. Change the property from “No” to “Yes”, as shown in Figure 1. After changing the property, the SQL Server service will need to be restarted to enable Instant File Initialization. You can also disable instant file initialization by setting the property to “No” in SQL Server Configuration Manager and restarting the instance.

Which files are getting zeroed out?

When instant file initialization is disabled, both the data files and the transaction log files will be zeroed out when a database is created, restored, or file space is added. However, if instant file initialization is enabled then, only the transaction log is zeroed out during these operations.

You can see additional messages in the error log to identify which files are zeroed out by turning on trace flags 3004 and 3605. Trace flag 3004 is undocumented and tells SQL Server to show information about backups and file creation in the error log file. Whereas trace flag 3605 is documented, it tells the database engine to write the trace messages to the error log file. When these two trace flags are turned on, you can review the error log to see which files are being zeroed out when a new database is created, or additional space is allocated to a database. Additionally, messages about which files are zeroed out are even shown when a database is restored. Keep in mind that these trace flags should only be used for testing and not left on continually in production.

In Listing 2 shows the TSQL code that turns on trace flags 3004 and 3605.

Listing 2: Turning on trace flags 3004, and 3605

DBCC TRACEON(3004 ,3605 ,-1);
GO

When these two trace flags are enabled and instant file initialization is disabled, the messages in Report 2 are displayed when creating a new database named TestIFI.

Report 2: Informational messages about zeroing out database files

Zeroing C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\DATA\TestIFI.mdf from page 0 to 1024 (0x0 to 0x800000)

Zeroing completed on C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\DATA\TestIFI.mdf (elapsed = 8 ms)

Zeroing C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\DATA\TestIFI_log.ldf from page 0 to 1024 (0x0 to 0x800000)

Zeroing completed on C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\DATA\TestIFI_log.ldf (elapsed = 7 ms)

Starting up database ‘TestIFI’.

Parallel redo is started for database ‘TestIFI’ with worker pool size [6].

FixupLogTail(progress) zeroing 2 from 0x5000 to 0x6000.

Zeroing C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\DATA\TestIFI_log.ldf from page 3 to 249 (0x6000 to 0x1f2000)

Zeroing completed on C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\DATA\TestIFI_log.ldf (elapsed = 1 ms)

Report 2 shows that the pages in the data (mdf) and log (ldf) files were zeroed out.

If instant file initialization is enabled and these trace flags are turned on, only the transaction log file will get zeroed out, as shown in Report 3.

Report 3: Informational messages about zeroing out database file

Zeroing C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\DATA\TestIFI_log.ldf from page 0 to 1024 (0x0 to 0x800000)

Zeroing completed on C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\DATA\TestIFI_log.ldf (elapsed = 3 ms)

Starting up database ‘TestIFI’.

Parallel redo is started for database ‘TestIFI’ with worker pool size [6].

FixupLogTail(progress) zeroing 2 from 0x5000 to 0x6000.

Zeroing C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\DATA\TestIFI_log.ldf from page 3 to 249 (0x6000 to 0x1f2000)

Zeroing completed on C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\DATA\TestIFI_log.ldf (elapsed = 1 ms)

Additionally, timestamps associated with these messages, which I didn’t include in the report, can show the performance impact of instant file initialization.

Improving performance with instant file initialization

The reason to turn on instant file initialization is to improve performance when disk space is allocated to a database. This writing of binary zeroes requires I/O and CPU to perform this initialization. This means that each time SQL Server allocates disk space to a database, there is a performance impact. The performance impact is minimal when small amounts of space are added, but when large amounts of space need to be zeroed out, the performance impact can be significant and can be noticed by those using the database.

To measure the performance impact, I will perform two tests. My test will create a database, first with instant file initialization enabled and then a second time with instant file initialization disabled. For this test, I will create a database named “PerformanceIFI” using the TSQL code in Listing 3.

Listing 3: Code used for performance testing

CREATE DATABASE [PerformanceIFI]
 CONTAINMENT = NONE
 ON  PRIMARY 
( NAME = N'PerformanceIFI', 
  FILENAME = N'C:\temp\PerformanceIFI.mdf' , 
  SIZE = 40GB)
 LOG ON 
( NAME = N'PerformanceIFI_log', 
  FILENAME = N'C:\temp\PerformanceIFI_log.ldf' , 
  SIZE = 10GB)
GO

When running the code in Listing 3 with instant file initialization disabled, the messages in Report 4 were written to the error log file.

Report 4: Timing for zeroing out mdf and ldf when instant file initialization is disabled

When running the code in Listing 3 a second time with instant file initialization enabled, the messages in Report 5 were found in the error log file.Report 4 shows that it took 51,192 ms to zero out the mdf file and 15,090 ms to zero out the ldf file.

Report 5: Timing for zeroing out only ldf file when instant file initialization is disabled

By turning on the trace flags and running the two tests, it’s easy to determine the performance impact of zeroing out the data file when creating a database. The larger the data file, the more significant the performance impact.Report 5 shows no zeroing out messages for the mdf file, but the log file still got zeroed out.

Security Issue

There is a minor security issue when instant file initialization is enabled. Because data files are not zeroed out, the old information that was on the unused disk space is available for DBAs or another administrator who has access to see. Meaning, someone could peek in and look at this uninitialized data that was added to a database. If anyone did browse these uninitialized bytes of database data pages, then they might be able to access sensitive data they have not been authorized to see. If there are any concerns over DBAs or anyone else seeing old disk data in the uninitialized data pages, then instant file initialization should not be enabled.

Instant file initialization

Instant file initialization is a great way to improve performance when disk space is allocated to a database. With the changes to the installation process and the configuration management tool, the SQL Server team at Microsoft has made it simple to enable instant file initialization. If there is a need to optimize performance of disk allocations, and there are no concerns over the security issues associated with instant file initialization, then instant file initialization should be enabled. Are your instances configured to enable instant file initialization or not?

If you liked this article, you might also like Managing SQL Server transaction log size.

The post Improving performance with instant file initialization appeared first on Simple Talk.



from Simple Talk https://ift.tt/3rCiXez
via

Thursday, July 22, 2021

How to query private blob storage with SQL and Azure Synapse

The series so far:

  1. How to query blob storage with SQL using Azure Synapse
  2. How to query private blob storage with SQL and Azure Synapse

The queries from the previous article were made against the public container in the blob storage. However, if the container is private, you will need to authenticate with the container. In this article, you’ll learn how to query private blob storage with SQL.

NOTE: Be sure that the Azure Synapse Workspace and the storage account with the sample files are set up before following along with this article. You will also need to replace your storage account URL each time that a storage account URL is used in the article.

There are three possible authentication methods, and these methods may have some variation according to the type of storage account and the access configuration. I will not dig into details about storage here and leave that for a future article.

The three authentication methods are:

Key: The storage account has two keys. If you provide one of the keys during the connection, you have access to the storage account.

Shared Access Key (SAS): A SAS key is used in a similar way to the key; however, it has additional benefits, such as:

  • It has an expiration date
  • It has limits of what permissions the user can have

Azure AD: You can make the authentication using Azure Active Directory.

These authentication methods are supported in general by storage accounts. However, SQL Server On Demand has limitations about what authentication methods it supports for the storage: SAS and Azure AD using Pass Through.

Authenticating with SAS key

The first method to try is with the SAS key. Follow along with key management next.

Understanding key management

First, you must understand how to generate the SAS key. Each storage account has two main keys used to control access to the storage. Using these keys is one of the methods of access, also called SAK.

The accounts have two keys to allow creating a management policy over them. At any moment, you can revoke one of the keys, and every client application using that key directly or indirectly will lose access.

On the left side panel of the storage account, under settings, you can see the Access Keys option. On this option, you find the two main keys, and you can revoke them, generate new keys and cancel the access of all applications depending on these keys, directly or indirectly.

Image showing access keys

You may also notice the Shared Access Signature option on the left. That’s where you can generate SAS keys. The SAS keys are encrypted by one of the main storage account keys. If you regenerate the main SAK used for the encryption, all the SAS keys encrypted by it will become invalid.

Good Practice

One good practice is always using the secondary SAK to generate the SAS keys because you will be able to regenerate the secondary SAK to invalidate all SAS keys without stopping critical applications, making access directly with the primary SAK.

You also can break down your applications by how critical they are and generate their SAS keys based on the primary or secondary SAK.

However, you only have two SAKs available. Regenerating one of them will drop the connection from many applications. The best option is still relying on the SAS expiration date.

Generating and managing the SAS key

The basic and most important detail you need to know about the keys is: They are not managed by Azure. This means that once you generate a SAS key, you can’t cancel the key directly. The only way to cancel the SAS key is regenerating one of the SAKs, which then creates a cascading effect on other keys, as explained before.

As a result, the rule is simple: Take care of the SAS keys you generate.

Once you click on the Shared Access Signature page, you will need to fill in the following details to build your SAS key:

  • Allowed Services. In this example, you only need the blob service.
  • Allowed Resource Type. Here you specify what API level you would like. Service Management, Containers Management or Objects. You need objects, of course. However, the List Blobs API is related to the Containers resource type. For this example, you don’t need to enable containers management and creation. However, you need the user of the key to be able to list the blobs in the container, so you also need to include the Containers resource type
  • Allowed Permissions. You will need the following permissions: Read, Write, List, Add, Create
  • Blob Versioning Permissions: This is not needed for the example.
  • Start/Expire date/time. It’s up to you to decide how your Azure key policy will be. Make sure that the key will not expire while following the example.
  • Allowed IP Addresses. You can limit the IP addresses that can use this key. If you are using a server application or service to access the blob storage, you can create IP restrictions to make the key more secure. If the service is on Azure, I would build a private network first. In this example, you are running the queries from the Synapse Workbench, so leave the field blank.
  • Allowed Protocols. By default, it only allows HTTPS access; this is good for security.
  • Preferred Routing Tier: This one is a bit more complex. Any option is ok for the example; the choice depends on the rules of your environment. See the section below for more information about this functionality.
  • Signature Key. You can choose any key; it’s not important for the result. This gives you the freedom to plan your storage key policy in the way you prefer.

Image showing properties when creating the key

Preferred Routing Tier

Microsoft has a worldwide network linked to Azure. When you try to access something hosted inside Azure, there are two access possibilities. These possibilities are related to the concept of POP – Point of Preference.

The POP is the network access where your packages will get into Microsoft Network. It can be the closest POP to the client trying to access or the closest POP to the storage location.

POP Close to the client: The package will get into Microsoft Network as soon as possible, which means it will be faster and safer, but it will increase your networking costs.

POP Close to the Storage: The package will move through the internet and only get into Microsoft Network at the last moment possible. This will reduce the networking costs, reduce the security, and the performance may vary a lot.

In order to use these options, you need first to enable them on the Network tab. By doing so, Azure creates different URL’s to the storage account, one for the Microsoft Network routing and another for Internet Routing.

Image showing networking

When you choose to enable the SAS key for one of these routing options, it’s not only a matter of the key, but also the URL. The key will only work with the correct URL

After clicking the Generate button, the resulting SAS key is a set for querystring parameters to be used in a URL, as part of the querystring. You may notice the following details:

  • Each SAS parameter is a pair of ‘key=value’ data in the querystring
  • The querystring is already URL encoded, so you can include it in a URL
  • The querystring is provided with a starting ‘?’, which is exactly what may be needed to include it in a URL. The question mark is not part of the SAS key; some places accept the question mark, others require the question mark to be removed.
  • The sig parameter is the heart of SAS key. It’s a value encrypted by the storage key chosen, ensuring the security of the access.
  • The sv parameter points to the version of the SAS key generator. There are tools able to support up to some specific versions. This example will not face this challenge.

Image showing keys

Save the generated SAS token as you will need it later.

Configuring the serverless Pool

In order to store the SAS key safely in Synapse, you use Credential objects. SQL Server supports two kinds of Credential objects:

  • Server level Credential
  • Database Level Credential, introduced in SQL Server 2016

The difference is simple: Database level credential provides you more flexibility to move the solution and the database, while server-level credential is re-usable for all databases on the server.

The Credential object is important because it’s part of the encryption structure in SQL Server to keep critical information, such as the SAS key, safe.

Recommended additional reading: https://docs.microsoft.com/en-us/sql/relational-databases/security/encryption/encryption-hierarchy?view=sql-server-ver15&WT.mc_id=DP-MVP-4014132

The credential will not solve the problem by itself: the OPENROWSET doesn’t accept the credential object directly. You need to create at least one more object, called External Objects.

The two existing databases on the Serverless pool can’t be used for that. The existing databases are:

Master: The traditional SQL Server master database

Default: A database to integrate Spark Pool and SQL On Demand Pool. It’s a replicated database. Synapse has access to Spark data through this database.

You must create a new database to hold the objects needed. Call the database NYTaxi. Open Synapse Studio to run the script. (The first article in the series has the required steps)

According to the way SQL Server encryption works, you need to create a master key in the database, as you will learn by reading the link provided above. The master key will be responsible for the encryption of the objects inside the database, in this case, the credentials.

Creating the database and credentials (replace with your own SAS key token):

Create Database NYTaxi
GO
USE NYTAXI
GO
Create Master Key
GO
        
CREATE DATABASE SCOPED CREDENTIAL [MaltaCredential]
WITH IDENTITY='SHARED ACCESS SIGNATURE',  
SECRET = '?sv=2019-12-12&ss=bfqt&srt=sco&sp=rwdlacupx&se=2021-12-23T07:34:32Z&st=2020-11-21T23:34:32Z&spr=https&sig=mJpur3JCEp99w5OqHxcXSXGOh4g44rOZfl5j8%2B6St30%3D'
GO

Once the script is executed, the execution context will be on the NYTaxi database. You are still working on the Serverless Pool, but now with your own database.

Three details to highlight here:

  • The IDENTITY value is fixed, always SHARED ACCESS SIGNATURE when using SAS keys.
  • The SECRET includes the question mark ‘?’ . Some features expect the question mark, but others don’t.
  • The credentials are not visible on the UI. You need to use DMV’s to see the existing credentials

Checking the existing credentials on the database:

select * from sys.database_scoped_credentials

Image showing credentials in query

Serverless pool databases

You can look in detail at your new database using the second toolbar icon, located on the left side of the screen.

Image showing menu

The databases from the Serverless Pool are not easily visible, but once you use the Refresh on the action menu (“…” besides Databases), they will appear on the Databases list.

Image showing refresh

One very important difference between regular databases and the database on the Serverless Pool is you can’t create tables on the Serverless Pool. You can create what is called External Objects and views.

External Objects were introduced in SQL Server 2016 as part of the Polybase architecture, allowing access to data remotely from many different formats, especially unstructured formats. This doesn’t mean you are using Polybase: Synapse Serverless pool has a native provider for blob storage which takes a detour from Polybase.

External Objects are objects used to map external sources, such as blob storage, to SQL and allow you to query them like regular tables. The Serverless Pool will not be holding the data, only pointing to it. In the same way, you can create views over the External Objects, encapsulating more complex queries.

The image below shows exactly this: the database can’t have tables.

Image showing NYTaxi database

Creating the External Object

OPENROWSET doesn’t support the credential directly. You need to create an object called External Data Source to use the credential.

Create the external data source, replacing the URL with your URL from your storage created in the first article:

CREATE EXTERNAL DATA SOURCE [demoMalta] WITH 
(       
    LOCATION = 'https://lakedemo.blob.core.windows.net/datalake',
    CREDENTIAL=MaltaCredential
);
go

The LOCATION attribute uses HTTP/HTTPS protocol, the only protocol accepted by serverless pool to connect to blob storage.

You can check the created Data Source in the Databases window

OPENROWSET with External Data Source

The query below is the same already used in the previous article of this series, but modified to use the External Data Source:

select Month(Cast(Cast(DateId as Varchar) as Date)) Month,
        count(*) Trips
 from 
    OPENROWSET(
        BULK '/trips/',
        Data_Source='demoMalta',
        FORMAT='PARQUET'
    ) AS [rides]
group by Month(Cast(Cast(DateId as Varchar) as Date))
order by Month

Some details about this syntax:

  • The data source makes the syntax easier
  • You don’t specify the full URL anymore, only the final path
  • The data source holds the credential, but the data source can also be used on public access scenarios to make the syntax easier

The result is similar as before:

Image showing chart of results

Azure Active Directory pass-through authentication

Azure AD authentication is the default authentication method used by serverless pool. When you don’t provide any other authentication, Synapse will try to authenticate us using Azure AD.

The documentation mentions Pass-Through authentication as slower than SAS authentication. However, this is the kind of internal detail that changes every day. While I was writing this article, they had similar performance; sometimes, with Pass-Through having even better performance than SAS.

The requirement for the pass-through is very simple: The user making the query needs to have data permission over the storage.

Since it may not be a good idea to set this permission user by user, the best option is to create an Azure AD group, include the users needed in the group, and then give the permission to the group.

Create an AD group and set the permission

Follow these steps to create the AD group and give permissions:

  • On Azure Portal, open the main menu
  • Click Azure Active Directory

Image showing Azure AD

  • Under Manage, click Groups

Image shown manage menu

  • Click New Group button

Image showing groups

  • On Group Type dropdown, leave Security
  • On Group Name, you can choose any name; I will call the group DataLakeAdmins

Image showing new group

  • Under Members, click the No Members Selected link

  • In the Search box, type the name of the account you are using. Only part of the name may work; Azure will search for your account.
  • When you see your account below the Search box, select it

Image showing how to add members

  • Once your account is selected, click the Select button
  • Click the Create button, completing the group creation
  • Return to the Home of Azure Portal
  • Locate your storage account, LakeDemo, and click on it
  • Click Access Control (IAM) option on the left side menu

  • Click the Add button and the Add Role Assignment option

Image showing how to add role assignment

  • On Role dropdown, select Storage Blob Data Contributor

Select role

  • On the Select box, type the name of the group, in this case, DataLakeAdmins
  • Select the group as soon it appears below the Select box

  • Click the Save button to create the role assignment

Now you can execute the query on Synapse without providing the credential, using the pass-through authentication. Note that you will need to close out of the Synapse Workspace and open it again to get the refreshed permissions.

select Month(Cast(Cast(DateId as Varchar) as Date)) Month,
        count(*) Trips
 from 
    OPENROWSET(
        BULK 'https://lakedemo.blob.core.windows.net/datalake/trips/',
        FORMAT='PARQUET'
    ) AS [rides]
group by Month(Cast(Cast(DateId as Varchar) as Date))
order by Month

Analyzing the log and comparing the methods

One good way to compare both methods is by using Azure Monitor and Log Analytics to compare what happens with each authentication. Both are very important tools when working with Azure. You can read more about them in this other article https://www.red-gate.com/simple-talk/blogs/saving-money-log-analytics/

Once you have the diagnostics configuration enabled, a simple Kusto query can show what happens with each Synapse query execution.

Enabling Diagnostics on the storage account

The Diagnostics configuration in Storage Accounts is among the features that most change on Azure. At the time of this writing, there are two Diagnostics options in Storage Accounts.

Due to that, the steps below may change:

  1. Open the LakeDemo Storage Account on the portal
  2. Select Diagnostics Settings (preview) on the left tab

  1. Click on the Disabled button besides the blob row

List of storage account

  1. Click the Add Diagnostic Settings link

  1. On the left side, select all the information options to be sent to Log Analytics

  1. On the right side, select Log Analytics as the destination of the information

  1. On the Subscription drop down select the subscription where the Log Analytics workspace is located
  2. On the Log Analytics workspace dropdown, select the workspace which will hold the log information
  3. On the Diagnostic Settings Name textbox, type the name of the diagnostics configuration. In most types, there will be only one, and the name is not very important.

  1. Click on the Save button

Checking the logs

Execute this query on Synapse Serverless:

select Month(Cast(Cast(DateId as Varchar) as Date)) Month,
        count(*) Trips
 from 
    OPENROWSET(
        BULK '/trips/',
        Data_Source='demoMalta',
        FORMAT='PARQUET'
    ) AS [rides]
group by Month(Cast(Cast(DateId as Varchar) as Date))
order by Month
  1. Open the Storage Account in the portal
  2. Click on Logs (preview) on the left side menu

  • Close the Queries window that appears
  • It may take a few minutes before the log information is up to date. In the New Query 1 window, type the following Kusto query:
StorageBlobLogs
| where TimeGenerated > ago(1h)
| order by TimeGenerated desc 

Kusto query for logs

  • Click the Execute button

You may notice the following details on the result:

  • A single OPENROWSET query on Synapse over one folder with a single file generated six operations on the storage account

Log results

  • The AuthenticationType field appears as SAS – Shared Access Key
  • HTTP Status 206 means Partial Content. The GetBlob method returns the result in pieces, to control the transfer of big blobs
  • The total number of calls may vary due to the number of pieces GetBlob method returned
  • The RequesterTenantId field is empty because the AuthenticationType is SAS

Now, try the pass-through authentication. Execute the following query on Synapse Serverless:

select Month(Cast(Cast(DateId as Varchar) as Date)) Month,
        count(*) Trips
 from 
    OPENROWSET(
        BULK 'https://lakedemo.blob.core.windows.net/datalake/trips/',
        FORMAT='PARQUET'
    ) AS [rides]
group by Month(Cast(Cast(DateId as Varchar) as Date))
order by Month

Execute the Kusto query again on the storage log:

StorageBlobLogs
| where TimeGenerated > ago(1h)
|order by TimeGenerated desc 

results after AD

You may notice the following:

  • The query generates eight activities on the storage, two more than the SAS authenticated query
  • Synapse first tries to use anonymous access, generating the first two access and resulting in HTTP error 404. After the failed attempt, it reverts to pass-through
  • The pass-through authentication appears as OAUTH, the protocol used by Azure Active Directory
  • There is a bigger interval between the second failed request and the first OAUTH request (0.3ms). On this interval, Synapse was contacting Azure Active Directory and requesting the authentication
  • The RequesterTenentId field is now filled with the id of the tenant used for the authentication.

Query private storage with SQL

The two authentication methods create a safe way to access data in Azure storage. However, it could still be a bit better if you could use its own Synapse Managed Identity as an authentication method to the storage.

 

The post How to query private blob storage with SQL and Azure Synapse appeared first on Simple Talk.



from Simple Talk https://ift.tt/3BsrCos
via