ASP.NET Core 8 애플리케이션 성능 최적화 방법

아래는 최신 트렌드(.NET 8 기준)의 ASP.NET Core 애플리케이션 성능 최적화 방법을 단계별로 정리한 블로그 글 예시입니다. 각 단계별로 설명과 코드 샘플을 풍부히 담았으니, 필요에 따라 추가·변경하여 활용하세요.
1단계: 불필요한 서비스 등록 줄이기
ASP.NET Core의 미들웨어·서비스는 필요할 때만 등록해야 애플리케이션이 가벼워집니다. Program.cs에서 기본 템플릿이 등록해 주는 모든 것을 다 쓰는 것이 아니라, 실제 사용하는 것만 골라서 추가하세요.
var builder = WebApplication.CreateBuilder(args);
// 예: Identity나 Razor Pages를 안 쓰면 제거
// builder.Services.AddRazorPages();
// builder.Services.AddDefaultIdentity<IdentityUser>().AddEntityFrameworkStores<ApplicationDbContext>();
// 필요한 서비스만 등록
builder.Services.AddControllersWithViews();
builder.Services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
// 캐싱, 압축, 인증·권한 등은 필요 시에만
builder.Services.AddMemoryCache(); // Tip 2에서 사용
builder.Services.AddResponseCompression(); // Tip 5에서 사용
var app = builder.Build();
// 미들웨어도 필요한 것만
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.Run();
이렇게 구성 요소를 슬림화하면 애플리케이션 시작 속도가 빨라지고 메모리 사용량도 줄어듭니다.
2단계: 메모리 캐싱으로 DB 호출 횟수 줄이기
DB 호출은 디스크 I/O로 느립니다. 자주 읽지만 자주 변경되지 않는 데이터는 IMemoryCache(혹은 서버 팜 환경에는 Redis 등 분산 캐시)로 보관하세요.
// 1) Repository 클래스
public class CustomerRepository
{
private readonly IMemoryCache _cache;
public CustomerRepository(IMemoryCache cache) => _cache = cache;
public async Task<Customer> GetByIdAsync(string id, bool forceReload = false)
{
if (forceReload)
_cache.Remove(id);
return await _cache.GetOrCreateAsync(id, async e =>
{
e.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30);
return await LoadCustomerFromDbAsync(id);
});
}
private Task<Customer> LoadCustomerFromDbAsync(string id)
=> _dbContext.Customers.FirstOrDefaultAsync(c => c.Id == id);
}
// 2) Controller에서 사용
public class HomeController : Controller
{
private readonly CustomerRepository _repo;
public HomeController(IMemoryCache cache)
=> _repo = new CustomerRepository(cache);
public async Task<IActionResult> Index(string id, bool force = false)
{
var customer = await _repo.GetByIdAsync(id, force);
return View(customer);
}
}
- 분산 캐시: 서버 팜을 쓰면 Redis, NCache, SQL Server 분산 캐시를 활용하세요
3단계: 클라이언트로 전송하는 데이터 최소화
초기 페이지 로드 시 불필요한 데이터를 모두 내려주면 렌더링 지연이 발생합니다.
- 페이징(paging): 첫 페이지 데이터만 내려주고, 사용자가 요청할 때 추가 로드
- Infinite Scroll: 화면에 보이는 영역 + α 만큼만 로드
<!-- View: 최소 데이터만 렌더링 -->
<div id="grid"></div>
<script>
let page = 1, pageSize = 20;
async function loadPage() {
const res = await fetch(`/api/items?page=${page}&size=${pageSize}`);
const data = await res.json();
renderGrid(data);
}
loadPage();
// 스크롤 이벤트로 추가 로드...
</script>
// API: 페이징 지원
[HttpGet("api/items")]
public async Task<IActionResult> GetItems(int page = 1, int size = 20)
{
var items = await _dbContext.Items
.AsNoTracking()
.Skip((page - 1) * size)
.Take(size)
.ToListAsync();
return Ok(items);
}
페이징이나 무한 스크롤은 사용자 경험을 크게 개선합니다.
4단계: CSS·JS는 CDN에서, 정적 파일은 최적화된 경로로
- Bootstrap, React, Vue 등 산업 표준 라이브러리는 CDN 사용
- 회사 로고·커스텀 스크립트 등 자체 정적 파일도 Azure CDN 같은 서비스에 배포
<!-- _Layout.cshtml -->
<head>
<!-- Bootstrap CSS from CDN -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.4.0/dist/css/bootstrap.min.css" rel="stylesheet" />
<!-- WebOptimizer로 번들된 커스텀 CSS -->
<link rel="stylesheet" href="~/css/bundle.css" />
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.4.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/bundle.js"></script>
</body>
CDN으로 파일을 제공하면 지리적으로 더 가까운 노드에서 빠르게 다운로드됩니다.
5단계: 파일 번들링·압축(WebOptimizer 연동)
WebOptimizer를 사용하면 런타임에 자동으로 CSS/JS를 번들링·최소화(minify) 해주고, GZIP 압축까지 처리합니다.
// Program.cs
builder.Services.AddWebOptimizer(pipeline =>
{
pipeline.AddCssBundle("/css/bundle.css", "css/*.css");
pipeline.AddJsBundle("/js/bundle.js", "js/*.js");
});
var app = builder.Build();
app.UseWebOptimizer();
<!-- 번들 파일 참조 -->
<link rel="stylesheet" href="/css/bundle.css" />
<script src="/js/bundle.js"></script>
요청 수와 파일 크기를 동시에 줄여 초기 로드 속도를 높입니다.
6단계: ResponseCache로 서버 호출 횟수 줄이기
자주 요청되는 페이지에는 [ResponseCache]를 걸어 브라우저·프록시 캐싱을 활용하세요.
[ResponseCache(Duration = 600, VaryByQueryKeys = new[] { "country" })]
public IActionResult PriceList(string country)
{
var list = _dbContext.Prices.Where(p => p.Country == country).ToList();
return View(list);
}
같은 쿼리 문자열 요청 시 10분간 서버 호출 없이 캐시된 응답을 내려줍니다.
보너스: 전체 비동기(async/await) 활용
동기 메서드는 쓰레드를 블록해 요청 처리량을 제한합니다. 데이터베이스·HTTP 호출은 async 패턴으로 작성해 쓰레드 풀 풀링 효율을 극대화하세요.
public async Task<IActionResult> GetDataAsync()
{
var data = await _httpClient.GetFromJsonAsync<List<MyDto>>("https://api.example.com/data");
return View(data);
}
비동기를 통해 높은 동시성(concurrency)을 확보할 수 있습니다.
마무리
- 필요한 것만 등록
- DB 호출 줄이기 → 캐싱
- 클라이언트 전송 최소화
- CDN·번들링·압축
- 캐싱 및 비동기
이 여섯 가지+보너스 비동기를 적용하면 사용자 체감 속도가 크게 개선됩니다.
여기에 프로파일링(BenchmarkDotNet, MiniProfiler)이나 GC 튜닝(Workstation vs Server mode), Kestrel 옵션 조정 등 추가 최적화 기법을 더하면 더 좋은 성능을 얻을 수 있습니다.
참고사이트
https://www.telerik.com/blogs/make-run-faster-optimizing-aspnet-core-application
Make It Run Faster: Optimizing Your ASP.NET Core Application
Your users always like it when their applications run faster. Here are six things you can do right now to make that happen.
www.telerik.com