fullstack lx - improving your application performance
TRANSCRIPT
Optimizing your Web application
From 4 RPS to 200+ RPS in 90 minutes
Nuno Caneco @FullstackLX - 2017/04/06
Who am I
Nuno Caneco
Senior Software Engineer @ Talkdesk
/nunocaneco
File > New > Project
Project facts● Team: 5 devs + 1 team lead + 1 tester● Duration: 18x 2-week sprints
● 165 KLOC: 27 KLOC of which are automated tests
KLOC = 1.000 lines of code
Central
Local
WindowsPOS
Windows Mobile
terminal
The due date is coming...
depl
oy
So, we questioned ourselves
Will it handle data in PROD?
Will it be fast enough?
Will the database perform?
Will the users complain?
The purpose of this session
Share
the
preparatio
n
work
TALK IS CHEAP
SHOW ME THE CODE
Tip #1: Check the databaseINDEXES: ● Choose the indexes wisely● Use multi-column indexes● Pay attention to the order of the columns● Use Filtered Indexes● Use Included Columns
Stored Procedures / Functions: use them for the most data intensive features● And spend some time optimizing those!
Tip #2: Fine tune the PROD databaseDefaults are not always PROD ready
FILEGROUPS:● Separate tables and indexes into different FILEGROUPS● Isolate large tables on different FILEGROUPS
Hard drives:● Allocate fastest hard drives to indexes and mission critical tables● Make sure the tempdb is on the fastest storage possible
Tip #3: Know your ORMProfile the queries generated by the ORM:
● Find optimizations for the ORM-generated queries
● Make sure the queries are hitting INDEXES
Tip #4: Optimize data transfers● Use Stored Procedures for critical or high-load reads
● Use dedicated Types for Slim classes○ Results for Search or List operations
● Take care of the ORM query clauses
Tip #5: Lazy vs Eager loadingusing (var context = new DbContext()){
// SELECT * from Messages WHERE ID = @messageIdvar message = context.Messages.SingleOrDefault(m => m.ID == messageId);// SELECT * FROM User WHERE ID = @message.ToUserFKvar toUser = message.ToUser;
// Now imagine this on a loop...}
versus
using (var context = new DbContext()){
// SELECT * FROM Messages INNER JOIN Users ON (...)var message = context.Messages .Include("ToUser")
.SingleOrDefault(m => m.ID == messageId);}
Tip #6: Use Prepared StatementsUse Prepared Statements
● When performing queries to the database via ADO.NET/JDBC/ODBC○ It allows the database to cache and reuse Execution Plans among requests
● Avoid setting parameters on Strings via concatenation
var command = String.Format("SELECT * FROM Message WHERE FromUserFK = {0}", fromUserId);
versus
var command = new SqlCommand("SELECT * FROM Message WHERE FromUserFK = @fromUserFk", connection);command.Parameters.AddWithValue("@fromUserFK", fromUserId);
Tip #7: Cache stuffStore data in Cache
○ Reference tables○ Read-mostly data○ Recently read data
● Choose the most appropriate caching platform:○ Local Cache: MemCache (C#),○ Distributed Cache: Redis, memcached
But…● Measure the cost of querying the cache:
○ A round-trip to the network can take a few milliseconds
Tip #8: RAM is cheap, but...Avoid unnecessary data loads
○ Especially large data blocks
Stateless objects should be singletons
Optimize your code○ Avoid the ‘+’ to concatenate strings; Use StringBuilder() or String.Format() variants instead○ Optimize loops○ Avoid expensive operations○ Beware of data serialization○ Network consumes time
Tip #9: Mind the loggerLogging to Database causes LOCKs
and potential Exceptions on the application
Sync logging to disk causes contention → use async logging if you can
Use "Format" overloads to avoid unnecessary string concatenation
string[] operationResult = DoSomething();logger.DebugFormat("The result of this long running is {0}", string.Join(operationResult,";"));
// A better approachif(logger.IsDebugEnabled){ logger.DebugFormat(" The result of this long running is {0}", string.Join(operationResult,";"));}
Engineering is about balanceScalability
PerformanceFeatures & User Experience
Dev effort and timeOverall complexity
Maintainability
Cost vs Benefit
Q&A
Questions ?