주말 아침 - 주간 닷넷 #48

한 주 동안 .NET 생태계에서 있었던 주요 이슈와 아티클, 기술 트렌드를 정리해 소개합니다.


:pushpin: Device Bound Session Credentials 이해하기

  • 저자: Andrew Lock
  • 태그: #security #dbsc aspnetcore #cookies

주요 내용

  • 인증 쿠키가 bearer 토큰처럼 동작해 탈취되면 세션 하이재킹이 가능해지는 구조
  • 로그인 응답의 Secure-Session-Registration 헤더로 알고리즘(ES256, RS256), 등록 경로, 챌린지 전달
  • 브라우저가 TPM/보안 영역에 키 쌍을 생성하고 챌린지를 서명한 JWT를 Secure-Session-Response로 전송
  • 서버가 약 5분 수명의 단기 쿠키를 발급하고, 갱신 시 Secure-Session-Challenge와 Sec-Secure-Session-Id로 키 소유를 재증명
  • 구현 요소는 로그인 응답 헤더, 등록 엔드포인트, 갱신 엔드포인트 3가지이며 Chromium 전용, ASP.NET Core 프로토타입 PR 존재

:pushpin: 아무것도 사지 않고 세계 최악의 네트워크에서 모바일 앱을 쓸 만하게 만든 과정

https://medium.com/@mikhail.petrusheuski/what-it-took-to-make-a-mobile-app-usable-on-the-worlds-worst-networks-without-buying-anything-3a81cb1d15ea

  • 저자: Mikhail Petrusheuski
  • 태그: maui #performance #caching #networking

주요 내용

  • .NET MAUI 앱 PlayMaksi(인도·이집트·인도네시아 어린이 사용자)를 6주간 개선해 홈 로드 p90 26.2초에서 6.6초, 실패율 70.9%에서 32.4%로 감소
  • error_stage 이벤트 파라미터와 BigQuery events_* 테이블 분석으로 일부 국가 실패의 87%가 DNS 단계임을 확인
  • 2MB 이상 표지 이미지를 360/720/1080px WebP로 생성하고 키별 세마포어로 중복 생성 방지
  • 캐시 헤더를 max-age=3600에서 불변 키 기반 public, max-age=31536000, immutable + ETag로 변경, 동적 페이지는 브라우저 60초·엣지 300초·stale-while-revalidate 600초
  • 클라이언트 취소를 500이 아닌 499로 구분, DoH 폴백은 IP 고정으로 구현했으나 개선이 측정되지 않음

:pushpin: Kahuna가 단일 노드에서 초당 29만 요청에 도달한 방법

https://medium.com/@gutierrezandresfelipe/how-kahuna-reached-290k-requests-per-second-on-a-single-node-0aca41a79dbf

  • 저자: Andres Gutierrez
  • 태그: #kahuna #grpc #performance #actormodel

주요 내용

  • 8코어 Apple M3, 폐쇄 루프 워커 64개 기준 GET 290K req/s(Valkey 9.1은 247K req/s)
  • 병목은 저장소가 아닌 전송 계층으로, 작은 요청마다 gRPC 오버헤드가 발생. Request Frames로 독립 요청을 단일 스트림 메시지에 합쳐 처리량 4배
  • Fused Finalization으로 prepare·검증·commit을 한 액터 턴에 수행해 액터 메시지 6개를 1개로 축소
  • 속도 제한기 84K에서 약 200K 결정/초, RETURN 1 스크립트 105K에서 416~438K req/s, p50 700μs에서 284μs, p99 4ms에서 약 0.7ms
  • Fast path는 단일 임시 키, 스캔·명시적 트랜잭션 없음으로 제한되며, 남은 약점으로 lock API 개별 메시지와 2키 트랜잭션 19K/s 공개

:pushpin: ASP.NET Core의 Device Bound Session Credentials(DBSC) 실험적 지원: .NET 11 프리뷰 탐험 - 8부

  • 저자: Andrew Lock
  • 태그: #dotnet11 aspnetcore #security

주요 내용

  • 수명이 긴 인증 쿠키를 단기 쿠키로 교체하고, TPM에 보관된 개인 키로 서명한 챌린지 응답으로 갱신하는 DBSC 원리
  • 등록은 Secure-Session-Registration 헤더, 갱신은 Sec-Secure-Session-Id, Secure-Session-Challenge, Secure-Session-Response(JWT) 헤더로 진행
  • Microsoft.AspNetCore.Authentication.DeviceBoundSessions 패키지의 AddDeviceBoundSession("Application"), Identity에서는 sourceScheme: IdentityConstants.ApplicationScheme 지정
  • 내부적으로 {scheme}.Dbsc.Session, {scheme}.Dbsc.Refresh(/.well-known/dbsc 경로 한정) 쿠키와 PolicySchemeHandler 등록
  • HTTPS 필수, 브라우저 지원 한정, 광고 차단기 간섭, 개발자 도구에 요청이 보이지 않아 서버 로그로 디버깅

:pushpin: C# fixed buffer의 숨은 함정

  • 저자: Kevin Gosse
  • 태그: csharp #fixedbuffer #unsafe

주요 내용

  • 생성자가 없는 struct는 컴파일러가 initobj로 fixed buffer까지 0으로 초기화하지만, 사용자 정의 생성자가 있으면 fixed buffer가 초기화되지 않을 수 있음
  • C# 사양(§23.8.4)에서 fixed-size buffer는 definite assignment 검사 대상에서 제외
  • fixed long buffer[1] 예제에서 생성자 유무에 따라 출력이 0 0 0 0 0과 0 1 2 3 4로 갈림
  • C# 11의 자동 기본값 초기화와 결합했을 때의 영향
  • 대응 방법은 생성자에서 Span<T>.Clear()로 직접 지우거나 InlineArray로 이전

:pushpin: .NET 커스텀 객체 모델에 JsonPath 지원 추가하기

  • 저자: Gérald Barré
  • 태그: dotnet #jsonpath #meziantou

주요 내용

  • JsonNode로 변환하지 않고 강타입 C# 도메인 클래스에 직접 JSONPath 식(RFC 9535 기반)을 평가
  • Meziantou.Framework.JsonPath의 JsonPathNavigator<TValue>를 상속해 구현
  • 구현 대상 메서드는 GetKind(), TryGetPropertyValue(), GetProperties(), GetArrayLength(), TryGetElement(), TryGetString()/TryGetNumber()/TryGetBoolean()
  • ProductCatalog/Product/ProductMetadata 모델용 ProductCatalogNavigator 구현 예제(활성 상품 중 50유로 미만 이름 조회)
  • Evaluate()는 전체 매치, EvaluateValue()는 첫 매치를 반환하며 JsonPathEvaluationMode로 strict/lax 선택

:pushpin: .NET 11 Runtime Async: 계층 호출은 빨라지고 단일 호출은 느려진다

https://medium.com/@tolgayildiz91/net-11-runtime-async-faster-layers-slower-single-calls-cfbef1203dbc

  • 저자: Tolga Yildiz
  • 태그: #dotnet11 #runtimeasync #benchmark #performance

주요 내용

  • .NET 8, 10, 11 RC1(Runtime Async 비활성)을 비교하는 실험군 A와 .NET 11 RC1에서 변환 효과만 분리하는 실험군 B로 구성
  • Ryzen 9 9950X3D, Windows 11, BenchmarkDotNet 환경에서 TaskCompletionSource 게이트로 동기 완료를 막고 중단 상황을 통제
  • 4단계 계층 async에서 동기 완료 46% 빠름, 할당 352B에서 88B(75% 감소), 중단 시 26% 빠름
  • 단일 호출은 동기 Task 24% 느림, ValueTask 35% 느림, 통제된 중단 46% 느림(할당 200B에서 272B)
  • 활성화는 <Features>runtime-async=on</Features>, 검증은 메타데이터 비트 0x2000 및 AsyncStateMachineAttribute 부재 확인

:pushpin: C#에서 메모리 덤프 생성하기

  • 저자: Aaron Powell
  • 태그: #memorydump #diagnostics threadpool

주요 내용

  • ThreadPoolWatcher가 주기적으로 작업을 제출해 완료 지연이 임계값을 넘으면 덤프를 자동 생성
  • Windows는 dbghelp.dll의 MiniDumpWriteDump를 P/Invoke로 호출하며 덤프 크기 약 125MB
  • Linux는 런타임에 포함된 createdump를 하위 프로세스로 실행하며 덤프 크기 약 800MB
  • Yama LSM 환경에서는 PR_SET_PTRACER, 컨테이너에서는 --cap-add=SYS_PTRACE 필요
  • 댓글에서 지적된 사항: DllImport 대신 LibraryImport, 자기 자신 덤프 시 락 보유 스레드 정지로 인한 교착 가능성, 덤프 내 민감 정보

:pushpin: GC가 .NET 앱의 DI 누수를 막아주지 못하는 이유

https://satyampushkar.medium.com/why-garbage-collection-wont-save-your-net-app-from-di-leaks-ab6179dab259

주요 내용

  • GC 루트와 도달 가능성 기준으로 DI 수명과 GC 수명이 독립적으로 결정되는 구조
  • 루트 컨테이너에서 resolve한 IDisposable Transient를 컨테이너가 추적 목록에 보관해 해제되지 않는 사례
  • Singleton이 Scoped(IOrderRepository→DbContext)를 캡처하는 Captive Dependency
  • 제거 정책 없는 Singleton 컬렉션(ConcurrentDictionary)과 이벤트 구독 누수
  • ValidateScopes, ValidateOnBuild 옵션과 dotnet-counters, dotnet-dump, dotnet-gcdump, dotnet-monitor 진단 도구 사용

:pushpin: C#에서 UTF-16 문자열을 얼마나 빠르게 고칠 수 있는가

  • 저자: Daniel Lemire
  • 태그: #simd #utf16 #performance

주요 내용

  • 짝이 없거나 잘못된 surrogate는 ill-formed 문자열이므로 디스크·네트워크로 보내기 전에 정리해야 함
  • SimdUnicode 라이브러리의 IsWellFormed()(검사)와 ToWellFormed()(lone surrogate를 U+FFFD로 대체)
  • Intel Xeon Gold 6548N(AVX-512)에서 라틴 텍스트 검증 69 GB/s, IndexOfAnyInRange는 33 GB/s
  • 정상 이모지 입력에서 기존 방식은 0.4 GB/s로 떨어지고 SIMD는 53 GB/s 유지
  • 이미 정상인 문자열은 원본을 그대로 반환해 할당이 없으며 Apple M4 Max에서도 유사한 경향

:pushpin: C# 15 컬렉션 식 인수: C# 12, 13과 무엇이 다른가

  • 저자: Laurent Kempé
  • 태그: #csharp15 #collectionexpressions #language

주요 내용

  • 컬렉션 식 첫 요소로 with(...)를 두어 생성자·팩토리 인수를 전달([with(capacity: 100), 1, 2, 3])
  • C# 12(컬렉션 식, spread), C# 13(params 컬렉션 확장), C# 14(변경 없음), C# 15(with 인수) 버전별 비교 표
  • 용량 사전 지정, StringComparer.OrdinalIgnoreCase 전달, CollectionBuilder 특성의 커스텀 컬렉션 예제
  • 배열·span 미지원, dynamic 인수 불가, 타입 추론에는 영향 없음

:bookmark_tabs: 가벼운 읽을거리

후보 항목 중 이슈로 선정되지 않은 가벼운 읽을거리들


멀티 테넌트 모범 사례가 역효과를 낼 때

  • 암묵적 테넌트 컨텍스트가 HTTP 요청에서 백그라운드 큐 처리로 실행 컨텍스트가 바뀔 때 숨은 의존성 버그를 만드는 사례
  • 명시적 테넌트 ID와 암묵적 컨텍스트, 전역 쿼리 필터와 테넌트 간 작업용 우회로, 공유 DB와 테넌트별 DB, 기능 플래그와 모듈 분리의 트레이드오프 비교
  • 테넌트 식별을 어디서 명시할지, 컨텍스트 전환 시 무엇이 일어나는지, 어디서 경계를 넘어도 안전한지에 대한 설계 질문

PostgreSQL에서 SQL Server로: 날짜·시간 필드 노트

  • PostgreSQL timestamp(마이크로초 6자리, 시간대 없음)·timestamptz와 SQL Server datetime2(7)(100ns 정밀도, 6~8바이트)·datetimeoffset(오프셋 포함, 8~10바이트) 대응
  • timestamptz는 오프셋을 저장하지 않고 UTC로 정규화하지만 datetimeoffset은 입력 오프셋을 보존
  • AT TIME ZONE, SWITCHOFFSET() T-SQL 예제와 10억 행 기준 저장 공간 약 2GB 차이

다운스트림 API 하나가 서비스 전체를 무너뜨린 날

  • Payment API가 약 200ms에서 5~10초 이상으로 느려지고 Order API의 재시도가 트래픽을 늘려 장애가 커지는 시나리오
  • Circuit Breaker의 Closed·Open·Half-Open 상태, AddStandardResilienceHandler() 기본 설정과 CircuitBreakerStrategyOptions(실패율 50%, 최소 10건, 샘플링 30초, 차단 15초)
  • 멱등성 키 없는 재시도의 중복 처리 위험, 400대와 500대 오류 구분, 타임아웃·재시도·서킷 브레이커 역할 분리

구조체는 스택에 있지 않다: C#의 실제 메모리 관리 방식

  • 값 타입은 자신을 담는 컨테이너가 있는 곳에 위치한다는 점과 지역 변수, 클래스 필드, 구조체 배열, async/람다 캡처 시 힙 승격 사례
  • 정수 100만 개 박싱 시 약 24MB 힙 압박, 클래스 오브젝트 헤더(SyncBlockIndex + MethodTable 포인터 16바이트)
  • Span<T> 제로 할당 슬라이싱, ref struct 제약, readonly struct와 구조체 크기 16~24바이트 이하 권장

WinAppCLI v0.7.0 - Windows Sandbox 자동화, Native AOT 지원, 단일 파일 애플리케이션 등

  • --on sandbox 플래그로 Windows Sandbox에서 앱을 실행·디버깅하고 winapp target 명령으로 게스트 관리, 파일 전송, 데스크톱 캡처
  • winapp run . --aot로 Native AOT 앱을 별도 publish 없이 빌드·실행(x64, ARM64), #:property 지시문으로 .cs 단일 파일을 패키지 ID와 함께 실행
  • winapp pack이 .NET 프로젝트의 빌드·패키징·서명을 수행하고 winapp new --template reactor로 MVU 방식 순수 C# WinUI 앱 생성(실험적, .NET 10 필요)
3개의 좋아요