Hello, I tried to create a sample app, at first as you say it works fine. here is the appservice that i have used.
public async Task CreateForSeparateDb(Guid bookId, Guid tenantId)
{
var book = await _bookRepository.GetAsync(bookId);
var tenantBooks=new List<Book>();
using (CurrentTenant.Change(tenantId))
{
var tenantBook = new Book(GuidGenerator.Create(), book.Name + " Tenant", tenantId); //adding local event in book aggregate root.
tenantBooks.Add(tenantBook);
await _bookRepository.BulkInsertAsync(tenantBooks);
}
await _bookRepository.DeleteAsync(book);
await _localEventBus.PublishAsync(new BookDeletedEto()
{
Id = book.Id,
TenantId = null
});
}
Then when i go through my code, i realized that I am doing bulkinsert. I use ZEntityFramework Extensions nuget package. I named my method same as default abp repositories method (InsertManyAsync()) so i couldn't see it at first sight. Here is the code for my repository method.
public async Task BulkInsertAsync(List<Book> books,CancellationToken cancellationToken = default)
{
DbContext dbContext = (DbContext)(await GetDbContextAsync());
books.ForEach(o => { o.CreationTime = _clock.Now; });
await dbContext.BulkInsertAsync(books,
cancellationToken: GetCancellationToken(cancellationToken),
options:
(operation) => operation.BatchSize = 1000);
}
so dbcontext is coming right
The behavior is kind of confusing here. when i bulk insert with current tenant equals null, local events are triggered. When I change the current tenant to separate db tenant, it doesn't trigger for the entity that i create but instead it triggers for the entity that is already in host db with tenantid null.
after insert what i got in event handler.
I couldn't understand from where abp is trying to find the local events. When i look at the code it seems like it is trying to get the changed entities, and getting the local events from that entity.
maybe ZEntityFramework Extensions is not marking the entities as modified but if it is like that why it works on currentTenant=null scenario?
I can send you the sample app that i have created if you want.
Hello I want to serve different databases for different tenants and I know abp supports that and have a very good documentation about it. In my case i have a little bit different scenario.
I am building a server that multiple iot devices will hit the server and they are gonna be registered to host database (they do not belong to any tenant at that moment). Then the host tenant administrator needs to assign the devices to one of the tenants in the system from user interface.
So when it is assigned, i need to check if tenant has a separate db connection and according to that i will change the ICurrentTenant and insert a new data to tenant db. Then delete the data from host db. That is my plan. Sth like this.
public async Task ApproveForSeparateDbAsync(ApproveScreenForSeparateDbDto input)
{
var newScreens = new List<Screen>();
var screens = await _screenRepository.GetListByIdsAsync(input.ScreenIds);
using (CurrentTenant.Change(input.TenantId))
{
foreach (var screen in screens)
{
var newScreen = new Screen(GuidGenerator.Create(),
screen.Name,
screen.MacAddress,
screen.DeviceType,
screen.DeviceTypeVersion,
screen.ModelNumber,
screen.ApplicationVersion,
screen.IpAddress,
screen.Port,
screen.CurrentClock,
input.TenantId);
newScreen.Approve(input.TenantId,isSeparateDb:true);
newScreens.Add(newScreen);
}
await _screenRepository.InsertManyAsync(newScreens);
}
foreach (var screen in screens)
{
await _screenManager.DeleteWithEventAsync(screen);
}
}
Screen is a FullAuditedAggregateRoot with IMultiTenant interface.
here is screen constructor.
public Screen(
Guid id,
string name,
string macAddress,
string deviceType,
int deviceTypeVersion,
string modelNumber,
string applicationVersion,
string ipAddress,
int port,
DateTime currentClock,
Guid? tenantId = null) : base(id)
{
Check.NotNullOrWhiteSpace(macAddress, nameof(macAddress));
Check.NotNullOrWhiteSpace(deviceType, nameof(deviceType));
Check.NotNullOrWhiteSpace(modelNumber, nameof(modelNumber));
Check.Positive(deviceTypeVersion, nameof(deviceTypeVersion));
TenantId = tenantId;
UpdateName(name);
MacAddress = macAddress;
DeviceType = deviceType;
DeviceTypeVersion = deviceTypeVersion;
ModelNumber = modelNumber;
UpdateApplicationVersion(applicationVersion);
CurrentClock = currentClock;
IpAddress = ipAddress;
Port = port;
var openingHours = OpeningHoursHelper.GetDefault().Select(o => new OpeningHoursEto()
{
Id = o.Id,
Day = o.Day,
StartTime = o.StartTime,
EndTime = o.EndTime
}).ToList();
AddLocalEvent(new ScreenCreatedEto()
{
Id = id,
Name = name,
MacAddress = macAddress,
TenantId = TenantId,
OpeningHours = openingHours
});
}
you can see that over here i am trying to trigger an event so listeners can do the necessary work. when unitofwork completed it doesn't trigger any event. Weird part is if i do the same by injecting ILocalEventBus to appservice. It triggers the event. and you can see the local events on CurrentUnitOfWork.LocalEventHandlers.
Also if i change
using (CurrentTenant.Change(input.TenantId))
to
using (CurrentTenant.Change(null))
it also triggers the events. I suppose this is some kind of bug or sth i don't know when the current tenant has different Database.
Hello, Thanks for the answer.
public override void ConfigureServices(ServiceConfigurationContext context) { Configure(options => { options.ContentContributors.RemoveAll(x => x == typeof(DatabaseTemplateContentContributor)); }); }
I only want to remove this option when I use the specific templates, for the other templates like emailing, i think i need this. I don't think there is an option for that right now?
To overcome the problem for now i am adding prefix to the name of the templates that I want to exclude, and added another template content contributor
public class MyVirtualFileTemplateContentContributor : VirtualFileTemplateContentContributor, ITransientDependency
{
private readonly ILocalizedTemplateContentReaderFactory _localizedTemplateContentReaderFactory;
public MyVirtualFileTemplateContentContributor(
ILocalizedTemplateContentReaderFactory localizedTemplateContentReaderFactory) : base(
localizedTemplateContentReaderFactory)
{
_localizedTemplateContentReaderFactory = localizedTemplateContentReaderFactory;
}
public override async Task<string> GetOrNullAsync(TemplateContentContributorContext context)
{
if (!context.TemplateDefinition.Name.Contains(MyTemplateDefinitionProvider.TemplateGroup))
{
return null;
}
var localizedReader = await _localizedTemplateContentReaderFactory
.CreateAsync(context.TemplateDefinition);
return localizedReader.GetContentOrNull(
null
);
}
}
so i fixed the problem in that way.
For the second question, I am not so sure about if it is a redis problem. I do not understand why redis should have a bottleneck with 2000 requests. Also I am not sure why the redis trying to get the value of AbpExceptionHandling_en from Volo.Abp.LanguageManagement.Texts
redis-command = HMGET c:Volo.Abp.LanguageManagement.Texts,k:Adzup:AbpExceptionHandling_en
With DatabaseTemplateContentContributor, the code will go through and tries to get the value with localization from the repository, so every request that hits the backend will try to go to db first
here you can see that the code will check the db and since it is a static store it will return null. And this has been tried with localization "en" and couldn't find it. Next try will be with null, and in that case the same thing will happen from dbcontributor, if you have en-En culture it will do 3 times
so it will look twice to database at least. In this process i was thinking if every request is creating 2 different connection to db it takes longer time to get the result or maybe sth else.Then it is triggering the redis overload cause it seems like when exception is happening Abp is trying to find some localization parameters from redis cache.
At the end of this story, I fixed my problem by not going to db or redis at all, with implementing ITemplateContributor. Still not sure how much clients can my app serve if there is a need to go to db for crud operations or just for querying purpose. Should i create redis cluster? should i do postgres sharding or just do load balancing? I am so confused.
Is there any benchmark that has been done with abp template for stress testing? I don't want to deploy the app and having surprises.
Since it is gonna be iot devices that will connect to server it can increase the load very much. I need to find the sweet spot for 1 instance so i can do the decisions for sharding and clusters. I am not expecting more than 10 000 devices inside the system. So i was expecting to handle that much load with 1 instance.
Hello I am trying to find out bottlenecks inside my app with stress testing. I am preparing the server to handle more than 5000 device requests. It is going to be 1 get / post request in every 5 secs for each device. Each device is sending a soap xml format when they do post and when they do get, they are getting a response as xml from the server. To serve xml to device, I use Volo.Abp.TextTemplating.Scriban
I prepare my controllers to connect to database less frequently so i do not create bottleneck in database. To do that i use Microsoft Orleans. Holding the data in memory then saving it to database in every 5 mins since it is not a critical data that comes from the device. However for some critical data sometimes I need to persist the data, this is happening at first request from each device. I use k6 for stress test. I prepared a test for 3000 devices. I also make a setup for pgbouncer in front of postgresql for connection pooling. Things are okay until 2000 devices after this number I am getting this exception.
2023-10-16 22:03:31.077 +02:00 [ERR] The operation was canceled. System.OperationCanceledException: The operation was canceled. at System.Threading.CancellationToken.ThrowOperationCanceledException() at System.Threading.SemaphoreSlim.WaitUntilCountOrTimeoutAsync(TaskNode asyncWaiter, Int32 millisecondsTimeout, CancellationToken cancellationToken) at Volo.Abp.Threading.SemaphoreSlimExtensions.LockAsync(SemaphoreSlim semaphoreSlim, CancellationToken cancellationToken) at Volo.Abp.Caching.DistributedCache
2.GetOrAddAsync(TCacheKey key, Func
1 factory, Func1 optionsFactory, Nullable
1 hideErrors, Boolean considerUow, CancellationToken token) at Volo.Abp.TextTemplateManagement.TextTemplates.DatabaseTemplateContentContributor.GetOrNullAsync(TemplateContentContributorContext context) at Volo.Abp.TextTemplating.TemplateContentProvider.GetContentOrNullAsync(ITemplateContentContributor[] contributors, TemplateContentContributorContext context) at Volo.Abp.TextTemplating.TemplateContentProvider.GetContentOrNullAsync(TemplateDefinition templateDefinition, String cultureName, Boolean tryDefaults, Boolean useCurrentCultureIfCultureNameIsNull) at Volo.Abp.TextTemplating.TemplateRenderingEngineBase.GetContentOrNullAsync(TemplateDefinition templateDefinition) at Volo.Abp.TextTemplating.Scriban.ScribanTemplateRenderingEngine.RenderSingleTemplateAsync(TemplateDefinition templateDefinition, Dictionary2 globalContext, Object model) at Volo.Abp.TextTemplating.Scriban.ScribanTemplateRenderingEngine.RenderInternalAsync(String templateName, Dictionary
2 globalContext, Object model) at Volo.Abp.TextTemplating.Scriban.ScribanTemplateRenderingEngine.RenderAsync(String templateName, Object model, String cultureName, Dictionary2 globalContext) at Volo.Abp.TextTemplating.AbpTemplateRenderer.RenderAsync(String templateName, Object model, String cultureName, Dictionary
2 globalContext) at Doohlink.MagicInfo.Envelopes.Renderers.EnvelopeRenderingService1.RenderAsync(EnvelopeHeader header, TModel body) in C:\Development\Projects\Examples\Doohlink\aspnet-core\modules\Doohlink.MagicInfo\src\Doohlink.MagicInfo.Domain\Envelopes\Renderers\EnvelopeRenderingService.cs:line 30 at Doohlink.MagicInfo.Handlers.CommandHandler.HandleAsync(Envelope
1 envelope) in C:\Development\Projects\Examples\Doohlink\aspnet-core\modules\Doohlink.MagicInfo\src\Doohlink.MagicInfo.Application\Handlers\CommandHandler.cs:line 90 at Doohlink.MagicInfo.Handlers.EnvelopeHandler.HandlePostAsync(String body) in C:\Development\Projects\Examples\Doohlink\aspnet-core\modules\Doohlink.MagicInfo\src\Doohlink.MagicInfo.Application\Handlers\EnvelopeHandler.cs:line 62
and after the stress test finished, the application can not connect to redis anymore. i need to flush redis. Also you can see that inside the logs.
2023-10-16 22:42:47.163 +02:00 [WRN] The message timed out in the backlog attempting to send because no connection became available, command=HMGET, timeout: 5000, outbound: 0KiB, inbound: 0KiB, inst: 0, qu: 89, qs: 0, aw: True, bw: CheckingForTimeoutComplete, rs: ReadAsync, ws: Idle, in: 0, in-pipe: 0, out-pipe: 0, last-in: 0, cur-in: 0, sync-ops: 833, async-ops: 49078, serverEndpoint: localhost:6379, conn-sec: 1484.52, aoc: 1, mc: 1/1/0, mgr: 10 of 10 available, clientName: DESKTOP-NHAEDKT(SE.Redis-v2.6.122.38350), IOCP: (Busy=0,Free=1000,Min=1,Max=1000), WORKER: (Busy=47,Free=32720,Min=6,Max=32767), POOL: (Threads=52,QueuedItems=441,CompletedItems=2340351,Timers=7635), v: 2.6.122.38350 (Please take a look at this article for some common client-side issues that can cause timeouts: https://stackexchange.github.io/StackExchange.Redis/Timeouts) StackExchange.Redis.RedisTimeoutException: The message timed out in the backlog attempting to send because no connection became available, command=HMGET, timeout: 5000, outbound: 0KiB, inbound: 0KiB, inst: 0, qu: 89, qs: 0, aw: True, bw: CheckingForTimeoutComplete, rs: ReadAsync, ws: Idle, in: 0, in-pipe: 0, out-pipe: 0, last-in: 0, cur-in: 0, sync-ops: 833, async-ops: 49078, serverEndpoint: localhost:6379, conn-sec: 1484.52, aoc: 1, mc: 1/1/0, mgr: 10 of 10 available, clientName: DESKTOP-NHAEDKT(SE.Redis-v2.6.122.38350), IOCP: (Busy=0,Free=1000,Min=1,Max=1000), WORKER: (Busy=47,Free=32720,Min=6,Max=32767), POOL: (Threads=52,QueuedItems=441,CompletedItems=2340351,Timers=7635), v: 2.6.122.38350 (Please take a look at this article for some common client-side issues that can cause timeouts: https://stackexchange.github.io/StackExchange.Redis/Timeouts) at StackExchange.Redis.ConnectionMultiplexer.ExecuteSyncImpl[T](Message message, ResultProcessor
1 processor, ServerEndPoint server, T defaultValue) in /_/src/StackExchange.Redis/ConnectionMultiplexer.cs:line 2099 at StackExchange.Redis.RedisDatabase.HashGet(RedisKey key, RedisValue[] hashFields, CommandFlags flags) in /_/src/StackExchange.Redis/RedisDatabase.cs:line 405 at Microsoft.Extensions.Caching.StackExchangeRedis.RedisExtensions.HashMemberGet(IDatabase cache, String key, String[] members) at Microsoft.Extensions.Caching.StackExchangeRedis.RedisCache.GetAndRefresh(String key, Boolean getData) at Volo.Abp.Caching.DistributedCache
2.Get(TCacheKey key, Nullable`1 hideErrors, Boolean considerUow) 2023-10-16 22:42:47.163 +02:00 [WRN] ---------- Exception Data ---------- Redis-Message = HMGET c:Volo.Abp.LanguageManagement.Texts,k:Adzup:AbpExceptionHandling_en Redis-Timeout = 5000 Redis-Write-State = Idle Redis-Read-State = ReadAsync Redis-OutboundDeltaKB = 0KiB Redis-InboundDeltaKB = 0KiB Redis-OpsSinceLastHeartbeat = 0 Redis-Queue-Awaiting-Write = 89 Redis-Queue-Awaiting-Response = 0 Redis-Active-Writer = True Redis-Backlog-Writer = CheckingForTimeoutComplete Redis-Inbound-Bytes = 0 Redis-Inbound-Pipe-Bytes = 0 Redis-Outbound-Pipe-Bytes = 0 Redis-Last-Result-Bytes = 0 Redis-Inbound-Buffer-Bytes = 0 Redis-Sync-Ops = 833 Redis-Async-Ops = 49078 Redis-Server-Endpoint = localhost:6379 Redis-Server-Connected-Seconds = 1484.52 Redis-Abort-On-Connect = 1 Redis-Multiplexer-Connects = 1/1/0 Redis-Manager = 10 of 10 available Redis-Client-Name = DESKTOP-NHAEDKT(SE.Redis-v2.6.122.38350) Redis-ThreadPool-IO-Completion = (Busy=0,Free=1000,Min=1,Max=1000) Redis-ThreadPool-Workers = (Busy=47,Free=32720,Min=6,Max=32767) Redis-ThreadPool-Items = (Threads=52,QueuedItems=441,CompletedItems=2340351,Timers=7635) Redis-Busy-Workers = 47 Redis-Version = 2.6.122.38350 redis-command = HMGET c:Volo.Abp.LanguageManagement.Texts,k:Adzup:AbpExceptionHandling_en request-sent-status = WaitingInBacklog redis-server = localhost:6379
So couple of questions, - I understand from the exception that it is trying to get the template text from DatabaseTemplateContentContributor, at Volo.Abp.TextTemplateManagement.TextTemplates.DatabaseTemplateContentContributor.GetOrNullAsync(TemplateContentContributorContext context) I assume this is coming from Text Template Management Module (https://docs.abp.io/en/commercial/latest/modules/text-template-management#text-template-management-module) and since this is the last contributor, it is the first to try.
Since my text templates doesn't use any localization is it possible to use VirtualFileTemplateContentContributor instead? Of course I can add another ITemplateContentContributor. I just wonder if there is any other way to avoid trying the last contributor and only use VirtualFileTemplateContentContributor, since I don't want the code to go to redis cache or database at all. All I need is to get the template from virtual file system and replace it with the model.
- Also I wonder what is happening in this setup, Is the bottleneck happening from the redis cache or from the database connection? At first thought, I assume it is sth going on with Redis Cache, Cause if the templates are cached, it won't go to database after some time for the same template. But strange thing over here is if i increase the connection pool size in database, I can increase the concurrent users that the server handles. With 150 max connections and max pool size of 150 i can handle 2000 users, if i increase the pool size to 250 then the server can handle 3000 users with the same test. So it seems database connection pool has a role over here, but I am not expecting for the code to create a connection with database. Why it is happening? Can it be a bug when it tries to get the template from the cache? so it goes to database instead?
By the way I disabled auditlogging all over the application with this configuration. any help would be appreciated.
Configure<AbpAuditingOptions>(options =>
{
options.IsEnabled = false; //Disables the auditing system
});
Hello, I have sent it to you.
And I still think that since it is an anonymous call, it shouldn't depend on "Current Tenant Id" while you are downloading the file. Cause you already have a token id to download the file.
Hello, Can you try to impersonate from the admin side for the tenant and try to download? I think that's the problem.
I have sent the email, i have also added docker compose file for postgres and redis. you can check it out if you want.
Hello, I think razor text templating have a memory leak. And i suppose it is sth. with caching. To produce the problem here are the steps, I can also send a sample app for this.
abp new Acme.BookStore -u none -csf
abp add-package Volo.Abp.TextTemplating.Razor
public class Command
{
public bool ReportIndicate { get; set; }
public string CommandId { get; set; }
public string MoCmd { get; set; }
public MoSequence MoSequence { get; set; }
public Command()
{
}
public Command(bool reportIndicate, string commandId, string moCmd, MoSequence moSequence)
{
Check.NotNullOrWhiteSpace(commandId, nameof(commandId));
Check.NotNullOrWhiteSpace(moCmd, nameof(moCmd));
Check.NotNull(moSequence, nameof(moSequence));
ReportIndicate = reportIndicate;
CommandId = commandId;
MoCmd = moCmd;
MoSequence = moSequence;
}
}
public class MoSequence
{
public List<Mo> MoList { get; set; }
public MoSequence()
{
MoList = new List<Mo>();
}
public void AddMo(string moPath, string moValue)
{
MoList.Add(new Mo(moPath, moValue));
}
public void AddMo(string moPath)
{
MoList.Add(new Mo(moPath));
}
}
public class Mo
{
public string MoPath { get; set; }
public string MoValue { get; set; }
public Mo()
{
}
public Mo(string moPath)
{
MoPath = moPath;
}
public Mo(string moPath, string moValue)
{
MoPath = moPath;
MoValue = moValue;
}
}
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<Acme.BookStore.Models.Command>
<srm:COMMAND>
<srm:REPORT_INDICATE>@(Model.ReportIndicate ? "true" : "false")</srm:REPORT_INDICATE>
<srm:COMMAND_ID>@Model.CommandId</srm:COMMAND_ID>
<srm:MO_CMD>@Model.MoCmd</srm:MO_CMD>
<srm:MO_SEQUENCE>
@foreach (var item in Model.MoSequence.MoList)
{
<srm:MO>
<srm:MO_PATH>@item.MoPath</srm:MO_PATH>
@if (item.MoValue != null)
{
<srm:MO_VALUE>@item.MoValue</srm:MO_VALUE>
}
else
{
<srm:MO_VALUE />
}
</srm:MO>
}
</srm:MO_SEQUENCE>
</srm:COMMAND>
public class DemoTemplateDefinitionProvider : TemplateDefinitionProvider
{
public override void Define(ITemplateDefinitionContext context)
{
context.Add(
new TemplateDefinition("Demo") //template name: "Demo"
.WithRazorEngine()
.WithVirtualFilePath(
"/Template/Demo.cshtml", //template content path
isInlineLocalized: true
)
);
}
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpAutoMapperOptions>(options =>
{
options.AddMaps<BookStoreApplicationModule>();
});
Configure<AbpRazorTemplateCSharpCompilerOptions>(options =>
{
options.References.Add(MetadataReference.CreateFromFile(typeof(BookStoreApplicationModule).Assembly.Location));
});
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<BookStoreApplicationModule>("Acme.BookStore");
});
}
7) Create an appservice that will return the output.
public class TestAppService : BookStoreAppService, ITestAppService
{
private readonly ITemplateRenderer _templateRenderer;
public TestAppService(ITemplateRenderer templateRenderer)
{
_templateRenderer = templateRenderer;
}
public async Task<string> GetOutput()
{
var moSequence = new MoSequence();
moSequence.AddMo(".MO.MONITOR_OPERATION.BOOTSTRAP.DEV_CODE", "105");
moSequence.AddMo(".MO.MONITOR_OPERATION.BOOTSTRAP.DEV_TYPE", "SPLAYER" + Random.Shared.Next().ToString());
moSequence.AddMo(".MO.MONITOR_OPERATION.BOOTSTRAP.DEV_MDNM", "QB13R");
var command = new Command(false,
"31470ade5bc2fd42-60c7af5-4725-8703-33dd729f63cea7aa5e4fbc0e",
".MO.MONITOR_OPERATION.BOOTSTRAP",
moSequence);
var result = await _templateRenderer.RenderAsync(
"Demo", //the template name
command
);
return result;
}
}
<srm:COMMAND>
<srm:REPORT_INDICATE>false</srm:REPORT_INDICATE>
<srm:COMMAND_ID>31470ade5bc2fd42-60c7af5-4725-8703-33dd729f63cea7aa5e4fbc0e</srm:COMMAND_ID>
<srm:MO_CMD>.MO.MONITOR_OPERATION.BOOTSTRAP</srm:MO_CMD>
<srm:MO_SEQUENCE>
<srm:MO>
<srm:MO_PATH>.MO.MONITOR_OPERATION.BOOTSTRAP.DEV_CODE</srm:MO_PATH>
<srm:MO_VALUE>105</srm:MO_VALUE>
</srm:MO>
<srm:MO>
<srm:MO_PATH>.MO.MONITOR_OPERATION.BOOTSTRAP.DEV_TYPE</srm:MO_PATH>
<srm:MO_VALUE>SPLAYER737483572</srm:MO_VALUE>
</srm:MO>
<srm:MO>
<srm:MO_PATH>.MO.MONITOR_OPERATION.BOOTSTRAP.DEV_MDNM</srm:MO_PATH>
<srm:MO_VALUE>QB13R</srm:MO_VALUE>
</srm:MO>
</srm:MO_SEQUENCE>
</srm:COMMAND>
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
insecureSkipTLSVerify: true,
noConnectionReuse: false,
scenarios: {
per_vu_scenario: {
executor: "per-vu-iterations",
vus: 5,
iterations: 30,
startTime: "0s",
maxDuration: '2m',
},
},
};
export default function () {
// Here, we set the endpoint to test.
const response = http.get('https://localhost:44395/api/app/test/output');
// An assertion
check(response, {
'is status 200': (x) => x.status === 200
});
sleep(3);
}
k6 run load.js
and check the memory spike in dotmemory you will see sth similar like this.
I analyze this with dotnet-dump and dotmemory. It has lots of free memory in unmanaged memory heap. mostly strings. I didn't check the source code. It could be sth wrong from caching so it caches the same thing in every request, but not sure, didn't deep dive into it.
If i switch to Volo.Abp.TextTemplating.Scriban, this doesn't happen and you can see the memory steady, no spikes. I switch to scriban and change all my templates with it. Hope i could clarify the problem if you need a sample app i can send it if you give an email address.
Hello, If you want, I can send you a sample app if you give me an email address ok here are the steps.
Create the new project from abp cli
abp new Doohlink -t app-pro -u angular -dbms PostgreSQL --separate-auth-server -m maui -csf
Add Volo.FileManagement module (run the command inside aspnet-core folder)
abp add-module Volo.FileManagement
Arrange Postgres and Redis. Change appsettings.json according to that. (I use docker containers for that.)
Run Dbmigrator.
Run the Application (AuthServer and HttpApi.Host)
do yarn install in angular app.
Configure the angular app. Add Config Module and Feature Module.
run angular app with yarn start
upload an image.
i hope this is enough information, as i say if you can not reproduce i can send you the sample app.