TraceParser - Collecting and Importing
This video will give you a step by step introduction to configuring Dynamics AX for tracing, collecting traces from both the TracingCockpit and Client Configuration Utility and importing these traces to the TraceParser database.
If you do not have TraceParser installed, make sure to review the first video on installing TraceParser at http://blogs.msdn.com/axperf/archive/2008/07/18/traceparser-video-training-part-i-installation.aspx
Video: Trace Parser Collection and Importing
Registry Keys
Registry Keys for Dynamics AX Client can be found under HKCU\Software\Microsoft\Dynamics\5.0\Configuration
Registry Keys for Dynamics AX Server can be found under
HKLM\System\CurrentControlSet\Services\Dynamics Server\5.0\01\Configuration
If there is more than one instance of Dynamics Server running, you will want to find the appropriate instance.
Under each of these, will want to find a key named Current and note the value of this key. This is your active configuration for the Client. You should find a corresponding subkey under Configuration which houses the registry keys for your current installation. TraceParser specific keys are labeled with the trace prefix. An explanation of each key and its meaning is below:
|
traceallowclient |
Ignored for client, on server set to 0x1 to allow the client to turn on tracing |
|
tracebuffersize |
Size in MB of buffer space allocated for ETW |
|
traceeventsenabled |
A semicolon delimited list of event flags. These correspond to the trace events in the AX Client Configuration Utility |
|
tracemaxbuffers |
Maximum number of buffers to use for ETW. If you are getting any dropped event errors, you will want to increase this. |
|
tracemaxfilesize |
Maximum disk space usage in MB for the ETW tracefile. You will need to have enough free space to allocate a file of this size. |
|
traceminbuffers |
Minimum amount of buffers to allocate for ETW. If you are getting any dropped event errors, you will want to increase this. |
|
tracestart |
0x0 if tracing is currently stopped, or set to 0x1 when tracing is started |
|
tracexppmethodcalldepth |
Depth of the call stack to collect when tracing |
Trace Events
Which events you specify will affect what information is collected when tracing. By default, if you install TraceParser using the client integration, it will automatically turn on most events necessary for tracing. If you would like to define your own events, here is an explanation.
|
RPC round trips to server |
Remote Procedure Calls between Client and Server |
|
X++ method calls |
Method calls within the X++ language |
|
Function Calls |
X++ built in functions such as int2str |
|
Number of nested calls |
Depth of the X++ call stack when collecting traces |
|
SQL Statements |
Queries issues to the SQL Database |
|
Bind variables |
Parameters specified for each of the SQL queries |
|
Row fetch |
Number of rows returned by SQL |
|
Row fetch summary |
Same as row fetch, and includes timing information |
|
Connect and disconnect |
Connections and disconnections to SQL |
|
Transactions: TTSBegin, TTSCommit, TTSAbort |
Transaction statistics, not necessary if X++ method tracing is enabled |
Using LOGMAN To Query Trace Providers
Open a command prompt and type "logman -ets query providers". This will show you a list of running providers. When AX Tracing is turned on, you should see an output similar to below:
C:\>logman -ets query providers
Session Id File
-------------------------------------------------------------------------------
MSDTC_TRACE_SESSION 2 C:\WINDOWS\system32\MSDtc\trace\dtctrace.log
AX_TRACE_client_{CE6D980E-3F17-490A-951F-ECE4BE57A597} 3 c:\log\AOS_AXPTRACEDC_2712_20080731194343_client.trc
AX_TRACE_AOS_{CD8AE17D-33CA-4C75-8DC9-79DCA90387E7} 4 c:\log\AOS_AXPTRACEDC_2712_20080731194343.trc
The command completed successfully.
Coming Soon - Part III
I will show you how to analyze the traces that we just collected. This might be a 2 part series that starts with the basics and then covers the more advanced topics.
Future series:
- Automation (using X++, C# and Batch Files)
- Case Studies using TraceParser to identify issues
One of the most important aspects of writing managed code that interacts with AX through the Business Connector is cleaning up objects. Each AxaptaObject and AxaptaTable must have dispose called on it before going out of scope, or we’ll leak the object on the server with no way to clean it up. AxaptaObject and AxaptaTable do not implement Finalizers, so the onus is on the consumer of these APIs to do proper cleanup. If a session logs off, all of its objects are cleaned up regardless of whether or not they were disposed of properly, but if we have a long running session that isn’t properly disposing objects, we can quickly run the server out of memory causing it to crash. These problems can be notoriously hard to debug since there is no leaking happening on the Business Connector application, and there is little insight into what’s going on inside of the X++ runtime. We’ve also seen problems occur similar to this from straight X++ code when users put things into the GlobalCache and never take them out or continuously add to the global cache.
X++ does have an API to enumerate all objects on the Heap, and a form to do so as well. The form is called “SysHeapCheck” and can only be opened up from the AOT. Clicking the “Update” button will populate the current tab. This form, however, is only accessible from the Rich Client and does little to aggregate objects together, so it’s hard to visualize which, if any, objects are leaking. The other big issue with this form is that it only shows the objects that are alive on the Client, not those that are alive on the server, so if we’ve leaked a bunch of objects on the server this form won’t help us.
Figure 1: SySHeapCheck form

This information is retrieved from the API HeapCheck inside of the X++ Runtime. We can use this API to provide a better abstraction of this information and make it accessible from inside of X++ code. I’ve created a useful utility called Heap Dump. You can find it attached to this post.
Let’s take a look at how we’ll use this code. The Main method provides a sample of this API:
static void Main(Args _args)
{
Map result;
MapEnumerator enum;
str objectName, objectType;
boolean runningOnClient;
int aliveCount;
int totalObjects;
int totalCursors;
container _map;
;
[totalObjects, totalCursors, _map] = HeapDump::DumpAllObjects();
result = Map::create(_map);
enum = result.getEnumerator();
info(strfmt("Total Objects: %1", totalObjects));
info(strfmt("Total Cursors: %1", totalCursors));
while(enum.moveNext())
{
[objectName, runningOnClient, objectType] = enum.currentKey();
aliveCount = enum.currentValue();
info(strfmt("%1 %2 %3 %4", objectName, runningOnClient ? "Client" : "Server", objectType, aliveCount));
}
}
The DumpAllObjects returns a Container in the format of [<Total number of unfreed Objects>, <Total number of unfreed Cursors>, <Unfreed Object Map>]. The third parameter, the Map of Unfreed Objects, is of type Map(Types::Container, Types::Integer). The key to this map is a container in the format [<Type Name>, <1 if object is on Client, 0 if on the server>, <Cursor or Object>]. This implantation is a bit hairy, but it was done this way to maximize interoperability with .NET. If we’re in X++ and we’re looking at a leak from the rich client, we can just use the main method here.
We can call this from managed code to get the same information, as seen below.
The best way to avoid these problems to begin with is to use the “using” statement. You can find multiple examples of this in my code below. Every time I call a method that returns an AxaptaObject, AxaptaContainer, or AxaptaCursor, it’s wrapped in a using statement. By doing so, as soon as the object goes out of scope it gets cleaned up, and its memory freed.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Dynamics.BusinessConnectorNet;
namespace HeapDumpExample
{
static class LambdaHelpers
{
/// <summary>
/// Performs an action on each item in a list, used to shortcut a "foreach" loop
/// </summary>
/// <typeparam name="T">Type contained in List</typeparam>
/// <param name="collection">List to enumerate over</param>
/// <param name="action">Lambda Function to be performed on all elements in List</param>
static void ForEach<T>(this IList<T> collection, Action<T> action)
{
for (int i = 0; i < collection.Count; i++)
{
action(collection[i]);
}
}
}
struct HeapDumpEntry
{
public string TypeName {get; set;}
public bool IsRunningOnClient { get; set; }
public string ObjectType { get; set; }
public int AliveObjectCount { get; set; }
}
/// <summary>
/// Helper class to allow us to perform LINQ queries over the X++ Map returned from the HeapDump call
/// </summary>
class EnumerableAxaptaMap : IEnumerable<HeapDumpEntry>
{
private AxaptaObject mMap;
public AxaptaObject Map
{
get { return mMap; }
set
{
if (startedEnumerating)
{
throw new Exception("Can't change contianer once enumeration has started");
}
mMap = value;
}
}
bool startedEnumerating = false;
#region IEnumerable<HeapDumpEntry> Members
public IEnumerator<HeapDumpEntry> GetEnumerator()
{
startedEnumerating = true;
//Get an enumerator over the Map
using (var MapEnumerator = Map.Call("getEnumerator") as AxaptaObject)
{
//Enumerate over the Map
while ((Boolean)MapEnumerator.Call("moveNext"))
{
using (var key = MapEnumerator.Call("currentKey") as AxaptaContainer)
{
var value = (Int32)MapEnumerator.Call("currentValue");
//The key contains a container with Type and location iformation, the value contains the number of objects alive
yield return new HeapDumpEntry
{
TypeName = key.get_Item(1).ToString(),
IsRunningOnClient = Convert.ToBoolean(key.get_Item(2)),
ObjectType = key.get_Item(3).ToString(),
AliveObjectCount = value
};
}
}
yield break;
}
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
//Lazy default implementation
foreach (var x in this.ToList()) { yield return x; }
yield break;
}
#endregion
}
class Program
{
static void Main(string[] args)
{
int TotalNumObjects, TotalNumCursors;
List<HeapDumpEntry> AliveObjects;
var CreatedObjects = new List<AxaptaObject>();
using (Axapta session = new Axapta())
{
//Log onto AX
session.Logon("", "", "", "");
//Dump all objects created on Logon
Console.WriteLine("BaseLine");
WriteAliveObjectsToConsole(session, out TotalNumObjects, out TotalNumCursors, out AliveObjects);
//Create Lots of Objects and don't dispose them
for (int i = 0; i < 100; i++)
{
CreatedObjects.Add(session.CreateAxaptaObject("Activities"));
}
//Dump current list of objects
Console.WriteLine("Before Disposing Objects");
WriteAliveObjectsToConsole(session, out TotalNumObjects, out TotalNumCursors, out AliveObjects);
//Dispose all of the objects
CreatedObjects.ForEach(obj => obj.Dispose());
Console.WriteLine("After Disposing Objects");
//Dump current list of objects again
WriteAliveObjectsToConsole(session, out TotalNumObjects, out TotalNumCursors, out AliveObjects);
}
}
private static void WriteAliveObjectsToConsole(Axapta session, out int TotalNumObjects, out int TotalNumCursors, out List<HeapDumpEntry> AliveObjects)
{
DumpObjects(session, out TotalNumObjects, out TotalNumCursors, out AliveObjects);
Console.WriteLine("Total Number of Live Class Objects: {0}", TotalNumObjects);
Console.WriteLine("Total Number of Live Cursors: {0}", TotalNumCursors);
AliveObjects.ForEach(entry => Console.WriteLine("\t{0} {1} {2} {3}", entry.TypeName
, entry.IsRunningOnClient ? "Client" : "Server"
, entry.ObjectType
, entry.AliveObjectCount));
}
private static void DumpObjects(Axapta session, out int TotalNumObjects, out int TotalNumCursors, out List<HeapDumpEntry> AliveObjects)
{
TotalNumObjects = 0;
TotalNumCursors = 0;
AliveObjects = null;
using (var objectsConatiner = session.CallStaticClassMethod("HeapDump", "DumpAllObjects") as AxaptaContainer)
{
if (objectsConatiner != null)
{
TotalNumObjects = (Int32)objectsConatiner.get_Item(1);
TotalNumCursors = (Int32)objectsConatiner.get_Item(2);
using (AxaptaObject ObjectMap = session.CallStaticClassMethod("Map", "create", objectsConatiner.get_Item(3)) as AxaptaObject)
{
var MapEnumerable = new EnumerableAxaptaMap { Map = ObjectMap };
//We use an extension method to sort the list and put it to a list
AliveObjects = MapEnumerable.OrderByDescending(x => x.AliveObjectCount).ToList();
}
}
}
}
}
}
q Determine total size of data and transaction log required for tempdb to avoid autogrow, and number of data files required based on # of processors (logical or physical).
How:
Determine the number of processors exposed to SQL Server. Unless you are using an affinity mask (not covered here), this is the total number of processors you see in the Windows Task Manager Performance tab.
Why:
SQL Server creates one visible scheduler (for processing user requests) for each processor, and we generally want to maintain one tempdb data file per processor. This recommendation is based on performance testing on the Dynamics AX OLTP workload.
q Move tempdb primary data file and log file to high-speed storage (if available); at the same time, resize data file proportionally to total size, and resize log file to required size.
How:
Moving tempdb primary data and/or transaction log file must be performed in two steps: first, alter the database and move the files; second, restart SQL Server instance to complete the move. The primary tempdb data file cannot be moved while SQL Server instance is running. NOTE: tempdb data and transaction log files may reside on the same storage device.
See http://technet.microsoft.com/en-us/library/ms174269.aspx (Section G) for detailed instructions on how to move tempdb database files.
Why:
Isolating tempdb on its own storage can improve performance. However, it is more critical to separate user database data and transaction log files (see section below).
q Create additional data files depending on number of processor cores, of equal size, totaling the data size determined in previous step, so that the aggregate size of data files including the primary data file meets the total size requirement. All data files must be identical in size.
How:
Additional database data files can be created from SQL Server Management Studio (SSMS) UI or by using the ALTER DATABASE command.
Why:
Creating multiple files for tempdb data, even if these files reside on the same storage device, can improve performance of tempdb operations, especially for databases (such as the AX user database, see below) that run in Read-Committed Snapshot Isolation (RCSI) mode. RCSI stores row versions in tempdb.
References:
http://www.microsoft.com/technet/prodtechnol/sql/2005/workingwithtempdb.mspx contains a comprehensive set of Best Practices for managing the tempdb database.
q If space is available on the drive where tempdb files are allocated, configure auto grow in MB (100-500) rather than percent for both data and log files.
How:
Auto grow configuration can be set from the SSMS UI or by using ALTER TABLE command.
Why:
Auto grow should not be used for space management, but rather as a “relief valve” in case tempdb files exhaust their initially allocated space. If files auto grow, the initial allocation should be adjusted to avoid auto grow in the future. Configuring auto grow in MB rather than percent increments allow for more predictable space allocation, and helps avoid extremely small or large growth increments.
q Review tempdb data and log files to ensure that they are all sized correctly and that data files remain of equal size.
How:
The easiest way to check is to use the database properties -> files UI in SQL Server Management Studio; scan the data files size to ensure that they are the same and at their initial values. If not, it means more space was required and one or more files grew, and the initial files sizes may have to be adjusted accordingly to ensure that they files remain at the same size at all times.
q Set compatibility level to 90 (SQL 2005) or 100 (SQL 2008).
q Set Read-Committed Snapshot Isolation = true (cannot be performed through GUI).
How:
Execute the following command with no other active connections in the database:
ALTER DATABASE <ax database name>
SET READ_COMMITTED_SNAPSHOT ON;
Query the row in sys.sysdatabases and ensure that the column is_read_committed_snapshot_on = 1. Replace <ax database name> with the appropriate name.
Why:
Testing with Dynamics AX ERP shows superior performance when using Read Committed Snapshot Isolation.
References:
http://technet.microsoft.com/en-us/library/ms175095.aspx includes complete instruction on enabling RCSI.
http://technet.microsoft.com/en-us/library/ms188277.aspx includes a discussion contrasting Read Committed Snapshot Isolation and Read Committed Isolation.
q Set Auto-Create Statistics, Auto Update Statistics= True; set Auto Update Statistics Asynchronously = FALSE (this is a change from our previous recommendation)
How:
These settings can be enabled from SQL Server Management Studio (SSMS) UI or by issusing the appropriate ALTER DATABASE statement.
Why:
Testing with Dynamics AX ERP shows superior performance when using these options.
q Ensure Auto Shrink = False.
q If autogrow is configured, use MB (usually 100-500) rather than percent for both data and log files.
How:
Autogrow configuration can be set from SQL Server Management Studio (SSMS) UI or by issusing the appropriate ALTER or CREATE DATABASE statement.
Why:
When autogrow is specified in absolute increments rather than as a percent of the total size, the effects of autogrow are easier to anticipate and manage. Percent-based autogrow setting can err on either extreme, of creating too many small file increments in rapid succession, or creating overly large increments.
Autogrow should be considered a “relief valve” to allow database files to grow when absolutely necessary, rather than causing an error, but it should not be used as a long-term storage management solution. When an autogrow event occurs, the DBA should respond by reviewing the size of all database files and adjusting accordingly, to minimize the probability of future autogrow events. Space should be periodically reviewed and adjusted when necessary based, rather than having the system periodically expand files on its own.
Physical Storage Configuration
Note: Compliance is optional depending on storage resources available. Some SAN vendors may have alternate recommendation that take precedence. Recommendations are listed in priority order.
q Perform sector alignment before allocating storage volumes to SQL Server on most SAN environments.
NOTE: The following recommendations for sector alignment do not apply to Windows Server 2008. Partitions created under Windows 2008 are aligned to sector 1024 by default, which is an acceptable setting.
How:
Consult with your SAN vendor if possible for specific guidance on the storage product you are using. In the absence of vendor-specific recommendations, volume alignment should be set on a 64K offset for SQL Server. See the Predeployment I/O Best Practices paper referenced below for detailed instructions. It is generally not necessary to perform volume alignment using Windows Server 2008, but check with your SAN vendor if in doubt.
Why:
Sector alignment (or volume alignment) ensures that logical disk sectors conform to physical sector boundaries on the disk geometry. Windows 2003 does not align sectors optimally for SQL Server workloads by default, causing performance problems because each logical sector spans two physical sectors.
References:
Predeployment I/O Best Practices
Physical Database Storage Design
q AX User database data files and transaction log files are on separate physical stores.
q Tempdb database files not on RAID 5 (RAID 1, 0+1, or 10 preferred).
q AX transaction log file is not on RAID 5 (RAID 1, 0+1, or 10 preferred).
q AX data files are not on RAID 5 (RAID 0+1, or 10 preferred).
q Tempdb database files are on separate physical store from user database files.
q Other database files (if any, such as databases for performance monitoring) are on separate physical store from AX and Tempdb database files.
Welcome to the Dynamics Ax Performance Team's blog. We're putting together a team introduction and hope to have it posted within the next week or so. The first entries will discuss SQL Server 2005 configuration and Best Practices for Dynamics Ax, but we'll be covering a much wider range of topics over the coming months, such as Dynamics AOS configuration, X++ profiling, and leveraging new features of SQL Server 2008.
My name is David Reinhold and I specialize in SQL Server database and BI performance. I spent nine years in Microsoft Consulting Services (MCS) practice before joining the Ax Performance Team in December, 2007.
Here is the Part 1 of the Database Configuration Checklist, covering configuration of: Operating System performance settings, SQL Server 2005/2008 Service configuration, and SQL Server 2005/2008 Instance configuration (updated to include special considerations for Windows Server 2008).
The format of the Checklist includes instructions on How to check and perform the configuration steps, Why we're making the recommendation, and References.
We've highlighted configurations that are specifically tuned for Ax databases, but there are also some general guidelines. Rather than re-invent the wheel, we've inlcuded links to authoritative SQL Server Best Practices documentation available on the web, primarily on Microsoft sites.
Database Configuration Checklist Part 1
Assumptions:
- Dedicated SQL Server 2005 Server (does not run any other major applications besides SQL Server)
- Single instance of SQL Server dedicated to running Dynamics Ax database.
q Set Advanced Performance Option->Processor scheduling: Best Performance of Background Services.
q Set Advanced Performance Option->Memory usage: Best Performance of Programs (does not apply to Windows Server 2008).
Windows Server 2003:
My Computer->Properties->Advanced tab->Performance->Settings button->Advanced tab->Choose Processor Scheduling-Adjust for best performance of Background Services, Memory Usage-Adjust for best performance of Programs.
Windows Server 2008:
My Computer->Properties->Advanced System Settings->Advanced tab->Performance Settings->Advanced tab->Choose How to Allocate Processor Resources->Adjust for Best performance of background services.
q Ensure boot.ini parameter /USEPMTIMER is present on AMD64 systems.
q Run SQL Server service under a Domain Account with minimum necessary privileges.
q Grant SQL Server service account “lock pages in memory” privilege (Local Security Policy).
q Enable TCP/IP network protocol.
q Disable Hyperthreading
How:
This must be performed in the bios settings of the server.
Why:
Not all SQL Server workloads benefit from hyperthreading. Dynamics AX ERP has not been extensively tested with hyperthreading, so the recommendation is to disable it.
References:
http://blogs.msdn.com/slavao/archive/2005/11/12/492119.aspx
Max Degree of Parallelism (MAXDOP instance-wide)
q OLTP (normal operations0: Set Max Degree of Parallelism to 1
q UPGRADE (when upgrading to a new release of Ax): Set Max Degree of Parallelism to lesser of {8, # of physical processor cores, # of physical processor cores per NUMA node}.
q Memory: Adjust MAX Server Memory (MB) according to available RAM (32/64 bit server).
NOTE: The following recommendations for configuring SQL Server:max server memory do not apply to Windows Server 2008, only to earlier versions of the OS (Windows Server 2003). Windows 2008 includes improvements to memory management that make overriding the default setting for maximum SQL Server memory unnecessary (assuming the host is a dedicated to running a single SQL Server instance); see http://support.microsoft.com/kb/918483.
|
Server RAM (MB) |
SQLServer:max server memory (MB) |
/3GB * |
/PAE* |
AWE* |
|
2,000 |
1,600 |
No |
No |
No |
|
4,000 |
3,000 |
Yes |
No |
No |
|
6,000 |
4,500 |
Yes |
Yes |
Yes |
|
8,000 |
6,500 |
Yes |
Yes |
Yes |
|
12,000 |
10,000 |
No |
Yes |
Yes |
|
16,000 |
14,000 |
No |
Yes |
Yes |
|
24,000 |
22,000 |
No |
Yes |
Yes |
|
32,000 |
30,000 |
No |
Yes |
Yes |
|
>32,000 <= 128,000 |
(available RAM) – 2,000 to 4,000 |
No |
Yes |
Yes |
|
>128,000 |
4,000 |
No |
Yes |
Yes |
*Denotes 32-bit only setting
Set Maximum Memory
How:
Use sp_configure to set the value of ‘max server memory (MB)’ as follows:
EXEC sp_configure 'max server memory (MB)', 10240;
RECONFIGURE WITH OVERRIDE;
GO
EXEC sp_configure;
This example sets the max server memory to 10,240MB (10GB). Replace the value 10240 with the desired value. Examine the output from the second sp_configure execution and confirm that the new value is in the run_value column.
Why:
Under the default setting, which is to use all available memory, SQL Server may leave no more than 128MB physical memory free, and may not release memory fast enough if needed by the Operating System. These settings generally assure that ample memory will be available. See note above – max server memory default setting can be used if SQL Server is running on Windows Server 2008 OS.
References:
http://blogs.msdn.com/slavao/archive/2006/11/13/q-a-does-sql-server-always-respond-to-memory-pressure.aspx
http://support.microsoft.com/kb/918483
Set AWE
NOTE: Only configure on 32-bit systems as specified.
How:
Use sp_configure to set the value of ‘awe enabled’ as follows:
Examine the output from the second sp_configure execution and confirm that the new value is in the run_value column. If the ‘lock pages in memory privilege’ has not been set for the service account, an error is raised if you attempt to enable AWE (see section above).
Why:
If AWE is not enabled, SQL Server will be unable to address more than 2GB or 3GB of memory (depending on /3GB switch), irrespective of how much physical RAM is available. When AWE is enabled, the additional memory over 2GB or 3GB is available to SQL Server for data cache buffers. 32-bit only, not applicable to native 64-bit SQL Server 2005 instances.
References:
http://msdn2.microsoft.com/en-us/library/ms190673.aspx
http://technet.microsoft.com/en-us/library/ms191481.aspx
http://technet.microsoft.com/en-us/library/ms187499.aspx
Set /PAE and /3GB switches in boot.ini
NOTE: Only configure on 32-bit systems as specified
How:
These options are set in the boot.ini file for the appropriate OS selection. If hot-swappable memory is supported by the hardware, the functionality enabled by the /PAE switch is automatically enabled.
Why:
/3GB is required to address memory >2GB on 32-bit systems, and /PAE is required to address memory >4GB on 32-bit systems. Enabling /3GB limits memory available to the OS to a maximum of 16GB, even if more physical RAM is installed..
References (see AWE section above)
q Monitor available memory to ensure that SQL Memory setting is correct.
How:
Use the sysmon performance counter Memory:Available Mbytes to determine if available memory drops below 500MB, especially for sustained periods. You may need to reduce SQL Server Max Memory if this happens regularly or for extended durations.
Why:
On a server with at least 4GB running a dedicated SQL Server workload, when available memory goes below 500MB, especially for sustained periods, the server may experience performance problems. It is out of scope of this document to provide guidance applicable in every such situation, but if available memory exhibits this pattern, further investigation and remediation may be necessary.
_____________
David Reinhold
Microsoft Dynamics Ax Performance & Scalability Engineer - Database