C
C#9mo ago
yusuke

❔ ✅ Can I make this faster or should I start looking at load balancing? tell me your api speeds too

Am I hitting the ceiling as far performance goes for web api and caching? So I have a .net 3.1(have no choice about this) web api application that gets data from the database Postgres and gets queried based on odata query strings in the url. I am also using imemorycache. My average speed without cache is 50-100ms and with caching is 20-40ms, this is the total time it takes for the rest api to fire. I was wondering is there more I can do? Or am I at a point where I need to start load balancing? Thank you everyone. From a previous post I learned a bit about k6, currently I am only able to handle 60 requests concurrently. I get status code 500 that the server is just unresponsive. code: [HttpGet] [ODataRoute("Stocks")] [ResponseCache(Duration = 600)] public async Task<IActionResult> Get(ODataQueryOptions<Stock> options) { try { var key = "Stocks-" + HttpContext.Request.QueryString; if (_memoryCache.TryGetValue(key, out List<Stock> query)) return await Task.FromResult<IActionResult>(Ok(query)); var settings = new ODataValidationSettings { MaxNodeCount = 10000 }; var cacheOptions = new MemoryCacheEntryOptions() .SetSlidingExpiration(TimeSpan.FromMinutes(5)) .SetAbsoluteExpiration(TimeSpan.FromMinutes(10)); var stockQuery = _stockService.GetStock(); stocks = options.GetEntitiesFromODataQuery(stockQuery, settings); _memoryCache.Set(key, stocks, cacheOptions); return await Task.FromResult<IActionResult>(Ok(stocks)); } catch (Exception exception) { throw new ApiGeneralException(exception.Message, exception); } }
63 Replies
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
MODiX
MODiX9mo ago
Posting Code Snippets To post a code snippet type the following: ```cs // code here ``` Notes: - Get an example by typing $codegif in the chat. - Change the language by replacing cs with the language of your choice (for example sql or cpp). - If your code is too long, you can post it to https://paste.mod.gg/ and share the link.
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
thanks! now I know
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
isn't not async
options.GetEntitiesFromODataQuery(stockQuery, settings)
options.GetEntitiesFromODataQuery(stockQuery, settings)
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
This gets an Iquerable<Stock> using efcore
var stockQuery = _stockService.GetStock();
var stockQuery = _stockService.GetStock();
This applies odata query
stocks = options.GetEntitiesFromODataQuery(stockQuery, settings);
stocks = options.GetEntitiesFromODataQuery(stockQuery, settings);
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
Pheubel
Pheubel9mo ago
The curse of being stuck on old af dotnet. It is not great, you miss out on general runtime optimizations, evolved language syntax and security fixes. Your boss should take the initiative to allow upgrading the framework to more modern standards. In the end it will save money by being more performant, meaning nore requests before the need to expand; a wider ranger of programmers and better iteration speed due to the more modern syntax
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
This method applies odata to the query and returns a ToList()
stocks = options.GetEntitiesFromODataQuery(stockQuery, settings);
stocks = options.GetEntitiesFromODataQuery(stockQuery, settings);
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
Yeah there is so much I want to change, I was 70 % with converting our code to dotnet 6 but we had things that had to be done so I had to leave the upgrade to dotnet 6, but will get onto that horse once I get this functional for now
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
ToList
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
Back then dotnet 7 wasn't even out.
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
ekim1987
ekim19879mo ago
.net 8 isnt out though? I see it is only available in November? Sounds like he needs this out before then? ¯\_(ツ)_/¯
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
so bottom line, upgrade?
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
the tables are indexed, db queries went from 400ms to 40ms My method use to be sync
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
yeah will take it all out, I thought that forcing a sync method to async will allow it to use more resources, but instead it doesn't so will take that out
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
everything is sync now
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
I understand thanks will change the efcore part where it processes odata query
stocks = options.GetEntitiesFromODataQuery(stockQuery, settings);
stocks = options.GetEntitiesFromODataQuery(stockQuery, settings);
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
public IQueryable<Stock> GetStock()
{
try
{
return WebDbContext.Stocks;
}
catch (Exception exception)
{
throw new ApiGeneralException(exception.Message, exception);
}
}
public IQueryable<Stock> GetStock()
{
try
{
return WebDbContext.Stocks;
}
catch (Exception exception)
{
throw new ApiGeneralException(exception.Message, exception);
}
}
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
public static List<TEntity> GetEntitiesFromODataQuery<TEntity>(
this ODataQueryOptions<TEntity> options,
IQueryable entityQueryable,
ODataValidationSettings settings = null,
ODataQuerySettings querySettings = null,
Action<TEntity> modifyResultsAction = null)
where TEntity : class, new()
{
try
{
return ODataQueryExtensions.ParseQueryableToGenericList<TEntity>(options.ApplyODataQuery<TEntity>(entityQueryable, settings, querySettings), modifyResultsAction);
}
catch (Exception ex)
{
throw new ApiGeneralException(ex.Message, ex);
}
}
public static List<TEntity> GetEntitiesFromODataQuery<TEntity>(
this ODataQueryOptions<TEntity> options,
IQueryable entityQueryable,
ODataValidationSettings settings = null,
ODataQuerySettings querySettings = null,
Action<TEntity> modifyResultsAction = null)
where TEntity : class, new()
{
try
{
return ODataQueryExtensions.ParseQueryableToGenericList<TEntity>(options.ApplyODataQuery<TEntity>(entityQueryable, settings, querySettings), modifyResultsAction);
}
catch (Exception ex)
{
throw new ApiGeneralException(ex.Message, ex);
}
}
Yes it is a dbset, does it matter, whether I use dbcontext.Stock or just specify the dbset?
yusuke
yusuke9mo ago
No description
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
thanks, how does directly calling the dbset differ from calling it from an injected service? I agree that it would be faster but how much? 1ms off the original time? We have our dbcontext within our service, _stockStock not sure how that would impact performance significantly
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
[HttpGet]
[ODataRoute("Stocks")]
[ResponseCache(Duration = 600)]
public IActionResult Get(ODataQueryOptions<Stock> options)
{
try
{
var key = "Stocks" + HttpContext.Request.QueryString;
if (_memoryCache.TryGetValue(key, out IQueryable<Stock> existingQuery))
return Ok(existingQuery);

options.Validate(new ODataValidationSettings { MaxNodeCount = 10000 });
var query = options.ApplyTo(_webDbContext.Stocks);
var cacheOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(5))
.SetAbsoluteExpiration(TimeSpan.FromMinutes(10));
_memoryCache.Set(key, query, cacheOptions);

return Ok(query);
}
catch (Exception exception)
{
throw new ApiGeneralException(exception.Message, exception);
}
}
[HttpGet]
[ODataRoute("Stocks")]
[ResponseCache(Duration = 600)]
public IActionResult Get(ODataQueryOptions<Stock> options)
{
try
{
var key = "Stocks" + HttpContext.Request.QueryString;
if (_memoryCache.TryGetValue(key, out IQueryable<Stock> existingQuery))
return Ok(existingQuery);

options.Validate(new ODataValidationSettings { MaxNodeCount = 10000 });
var query = options.ApplyTo(_webDbContext.Stocks);
var cacheOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(5))
.SetAbsoluteExpiration(TimeSpan.FromMinutes(10));
_memoryCache.Set(key, query, cacheOptions);

return Ok(query);
}
catch (Exception exception)
{
throw new ApiGeneralException(exception.Message, exception);
}
}
I have removed all abstractions and using odata directly and using the dbcontext directly
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
this part returns IQueryable with no type
var query = options.ApplyTo(_webDbContext.Stocks);
var query = options.ApplyTo(_webDbContext.Stocks);
how would I go about convert IQueryable to ToList, or better yet ToListAsync. An IQueryable with no type doesn't have a to list option.
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
will try that thanks and get back to you
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
I think I shot a bit too high including odata and Imemorycache, so instead here is a simple endpoint
[HttpGet("Stock/Search/{name}")]
[ResponseCache(Duration = 600)]
public async Task<IActionResult> GetStock(string name)
{
try
{
var stocks= await _webDbContext.Stocks.Where(x => x.Name.Equal(name).ToListAsync();
return Ok(stocks);
}
catch (Exception exception)
{
throw new ApiGeneralException(exception.Message, exception);
}
}
[HttpGet("Stock/Search/{name}")]
[ResponseCache(Duration = 600)]
public async Task<IActionResult> GetStock(string name)
{
try
{
var stocks= await _webDbContext.Stocks.Where(x => x.Name.Equal(name).ToListAsync();
return Ok(stocks);
}
catch (Exception exception)
{
throw new ApiGeneralException(exception.Message, exception);
}
}
Lets start from here. It retrieves data using efcore and its in an async method my bigger problem here still remains that despite this being a simple api, when I run this using something like k6 with 100 concurrent users hitting it for 2 minutes, it just fails or stalls. I can see my cpu and ram are low (below 20% for the cpu and below 50% for the ram) how ever when I set the target for 60 users or lower to hit this endpoint concurrently then its fine
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
I heard of OutputCache, I will upgrade it but with all the work I'd have to do it may take me around a month before we can talk further. but I guess no other way
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
oh you recommending Nick, he's videos are good.
yusuke
yusuke9mo ago
When using dotnet trace this is what I found even with it being async and all
No description
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
the database is on a different server, k6 is running on my local pc and im running it on debug mode will do a quick release and run it again
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
what if the code is running on desktop and k6 on a laptop?
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
all 3 are on different computers
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
I want to run the code locally because I can't make changes to the server where the production code resides.
Unknown User
Unknown User9mo ago
Message Not Public
Sign In & Join Server To View
yusuke
yusuke9mo ago
fair point
Accord
Accord9mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
yusuke
yusuke7mo ago
I just ended upgrading over to a newer dotnet project.
Accord
Accord7mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.