주말 아침 - 주간 닷넷 #43 (2026.08.30)

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


:pushpin: 17ms에서 0.04ms로 — 올바른 SQL 인덱스 설계하기

  • 저자: Milan Jovanović
  • 태그: #sql #postgresql #performance

주요 내용

  • 인덱스는 테이블 스키마가 아니라 실제 실행되는 쿼리에서 설계해야 한다는 원칙과 복합 인덱스 컬럼 순서(동등조건 → 정렬/범위), 최좌접두사 규칙
  • 단순 동등조건 검색에서 순차 스캔 17ms가 인덱스 스캔 0.6ms로 단축
  • 복합 조건+정렬 쿼리는 16.6ms에서 0.04ms로, EXPLAIN ANALYZE 실행 계획에 Index Scan using ix_comments_issue_user_date가 찍히는 과정 제시
  • 대시보드 LATERAL 서브쿼리에서 인덱스 컬럼 순서가 잘못되면 정렬이 6,537회 수행되며 436ms, 순서 최적화 후 0.5ms
  • 복합 인덱스는 100만 행당 30MB, 단일 컬럼 인덱스는 7MB로 저장 비용과 쓰기 성능 저하가 따르며 중복 인덱스를 제거해야 한다는 지침

:pushpin: 내 벤치마크는 라이브러리가 2배 빠르다고 했다. 아니었다.

https://baryodev.medium.com/my-benchmark-said-my-library-was-2x-faster-it-was-not-d6a6110bf354

  • 저자: Baryo Dev
  • 태그: #benchmarkdotnet csharp #performance

주요 내용

  • Mapsicle이 AutoMapper 대비 2.1배 빠르다고 주장했으나 재측정 결과 1.48배였고, 컬렉션 매핑은 오히려 1.33배 느림(2,428ns vs 1,823ns)
  • 손으로 작성한 Stopwatch 루프(10만 회 반복)가 290ns를 보고한 반면 BenchmarkDotNet은 33ns로, 9배 오차의 원인은 매핑이 아니라 할당·GC 압력을 측정하고 있었기 때문
  • CI 게이트를 BenchmarkDotNet 요약 데이터를 직접 읽도록 재구성하고, 보호 대상을 되돌려 게이트가 실제로 실패하는지 검증하는 절차 도입
  • Type.IsAssignableFrom이 암시적 변환을 인식하지 못해 int→long 매핑이 예외 없이 0을 반환하는 등 데이터 손상 버그 7개 발견
  • 근본 원인은 변환 로직이 세 곳에 중복 구현되어 있던 것

:pushpin: 초록 CI 배지는 .NET 패키지 무결성을 증명하지 않는다

  • 저자: Christopher D. Cavell
  • 태그: #supplychainsecurity #nuget dotnet

주요 내용

  • 초록 CI 배지가 증명하는 것(특정 환경에서의 빌드·테스트 성공)과 증명하지 못하는 것(게시된 패키지와 검증된 소스의 동일성)의 구분
  • 소스 커밋 → 의존성 → CI 워크플로 → 빌드 환경 → 패키지 생성 → 레지스트리 → 소비자로 이어지는 아티팩트 추적 경로와 각 경계에서 던져야 할 질문
  • ContinuousIntegrationBuild=true와 Source Link 기반 결정론적 빌드, NuGet 저자 서명과 리포지토리 서명의 구분(dotnet nuget verify), 체크섬이 같은 채널 교체 시 무력해지는 한계
  • gh attestation verify를 통한 빌드 provenance 검증과 packages.lock.json·액션 SHA 고정을 통한 의존성 고정
  • tj-actions/changed-files 공급망 공격(CVE-2025-30066)을 예로 SHA 고정의 효과와 한계, 저장소 규모에 비례한 검증 수준을 제시하는 8항목 체크리스트

:pushpin: .NET에서 머신의 전체 프로세서 개수 구하기

  • 저자: Andrew Lock
  • 태그: dotnet #performance #interop

주요 내용

  • Environment.ProcessorCount의 버전별 동작 차이 — .NET Framework는 호스트 전체 논리 프로세서를 반환하고, .NET 6+는 친화성·컨테이너 제한 하에서 사용 가능한 프로세서만 반환
  • 컨테이너·친화성 제한과 무관하게 머신의 물리적 전체 프로세서 수가 필요한 시나리오
  • Windows에서 P/Invoke로 GetActiveProcessorCount() 호출
  • macOS에서 sysctlbyname("hw.logicalcpu") 호출
  • Linux에서는 sysconf 대신 /sys/devices/system/cpu/online 파일을 직접 파싱해 엣지 케이스를 회피하는 이유

:pushpin: 용량은 숫자가 아니다 — Redis Lua로 구현하는 원자적 승인과 스케일아웃

https://medium.com/@m.marano2k14/capacity-is-not-a-number-atomic-admission-and-scale-out-with-redis-lua-in-net-1318c3b3a388

  • 저자: Marco Marano
  • 태그: #redis #distributedsystems dotnet

주요 내용

  • 두 제어 평면이 동일한 용량 스냅샷을 읽고 같은 결정론적 로직을 실행하면 같은 slot을 이중 점유하는 경쟁 조건
  • AvailableRunSlots/ReservedRunSlots/EffectiveAvailableRunSlots로 나눈 다차원 용량 모델과 웜 런타임 재사용 → 새 Pod 생성 → 백프레셔 우선순위
  • 개별 Redis 명령은 원자적이지만 여러 명령에 걸친 결정은 원자적이지 않으므로, ZREMRANGEBYSCORE·ZRANGE·ZCARD·ZADD·PEXPIRE를 하나의 Lua 스크립트로 묶어 경계 검사와 slot 점유를 단일 원자 연산으로 처리
  • Selection은 선택하고 Reservation은 소유하며 Creation은 변형한다는 세 연산의 역할 분리
  • 예약 실패 시 재시도 대신 인벤토리를 다시 구성해 재선택하고, 정렬 집합 점수를 만료 타임스탬프로 사용해 크래시된 제어 평면의 용량 누수를 방지

:pushpin: .NET 서비스 디스커버리 4가지 구현 방식

https://medium.com/@zand.arash83/service-discovery-and-service-mesh-in-net-part-2-four-ways-to-implement-discovery-c74d4304389f

  • 저자: Arash Zand
  • 태그: #servicediscovery #kubernetes #dapr

주요 내용

  • Kubernetes DNS 방식에서 TCP 연결 풀링으로 DNS 재해석이 일어나지 않는 문제와 SocketsHttpHandler 설정 해법
  • Microsoft.Extensions.ServiceDiscovery의 프로바이더 체인과 AddServiceDiscovery/AddStandardResilienceHandler 등록 순서 문제
  • Consul 등록(AgentServiceRegistration)과 헬스체크 기반 해석 핸들러 구현
  • Dapr 사이드카를 통한 서비스 호출에서 mTLS·재시도·분산 추적이 자동화되는 대신 발생하는 오버헤드
  • 네 방식의 복잡도·메타데이터·mTLS·개발환경 지원 비교표와 멱등성 키를 포함한 복원력 설정

:pushpin: Aspire로 BFF 보안 아키텍처 구현하고 배포하기

  • 저자: damienbod
  • 태그: #dotnetaspire #bff #security

주요 내용

  • Angular 프론트엔드 + ASP.NET Core BFF + Auth0 인증 + 다운스트림 보안 API 구성을 .NET Aspire AppHost로 배포
  • AppHost 클래스의 코드 기반 구성으로 각 애플리케이션을 매핑하는 방식
  • 개발 환경에서는 Angular 개발 서버를 통합하고 프로덕션에서는 빌드 산출물을 서빙하는 조건부 구성
  • Auth0 자격증명과 DPoP 키 파라미터 등록, 프로젝트 참조·환경변수 설정, YARP 역프록시 구성
  • 전체 코드는 damienbod/Auth0BffDpopApi 저장소에 공개

:pushpin: 비즈니스 규칙은 위키에 머물 필요가 없다 — Roslyn 분석기로 강제하기

https://medium.com/@pabasaramahindapala/business-rules-dont-have-to-live-in-a-wiki-page-enforcing-them-with-a-roslyn-analyzer-a10627874f5b

  • 저자: Pabasara Mahindapala
  • 태그: #roslyn csharp #staticanalysis

주요 내용

  • 다중 테넌트 ASP.NET Core 앱에서 사용자 권한과 비즈니스 접근 권한 두 계층 중 하나가 누락되면 발생하는 크로스테넌트 IDOR 취약점
  • [BusinessAuthorize] 속성과 CheckAgainstBusinessInRequest 플래그를 검사하는 DiagnosticAnalyzer 구현
  • 속성 검색 → 체크 비활성화 판정 → BusinessId 매개변수 탐지 → 진단 보고의 4단계 분석 로직
  • .editorconfig로 Warning에서 Error로 점진 전환하고 SuppressMessage로 예외 사유를 기록하는 도입 전략
  • .csprojOutputItemType="Analyzer" 설정 등 프로젝트 구성 방법

:pushpin: 이벤트 기반 아키텍처와 커플링 — 생각만큼 디커플링되지 않았다

  • 저자: Derek Comartin
  • 태그: #eventdriven #architecture #messaging

주요 내용

  • 메시지 브로커 도입이 느슨한 결합을 자동으로 보장하지 않으며 "CRUD over Pub/Sub"가 안티패턴이라는 지적
  • ShipmentStatusChanged 같은 CRUD성 이벤트가 내부 데이터 모델을 공개 API처럼 노출시켜 소비자가 상태값을 역공학하게 만드는 문제
  • ShipmentDispatched, ShipmentDelayed 등 명시적 비즈니스 이벤트로의 재설계
  • 도메인 이벤트(서비스 내부, 자유 변경)와 통합 이벤트(서비스 간, 계약·버전관리 필요)의 구분
  • CDC 사용 시 DB 변화를 그대로 노출하지 말고 공개 통합 이벤트로 번역하라는 원칙

:pushpin: Undo는 기능이다 — Saga 보상과 재조정 백스톱

https://medium.com/@ivanball76/undo-is-a-feature-saga-compensation-and-the-reconciliation-backstop-fa017f9591b8

  • 저자: Ivan Ball-llovera
  • 태그: #saga #compensation #idempotency

주요 내용

  • 체크아웃은 원자적이지만 결제 승인 웹훅이 비동기로 도착해 이전 단계 롤백이 필요한 문제
  • 보상 로직을 도메인 이벤트 핸들러로 분리하고 별도 DI 스코프에서 독립 커밋
  • Order.InventoryRestored 같은 DB 불변 마커로 멱등성을 보장하고 재고 복구와 동일한 SaveChanges에서 커밋
  • RowVersion 낙관적 동시성 토큰으로 동시 재배달 처리
  • 프레임워크 없이 적용할 수 있는 6단계 원칙 별도 정리

:pushpin: 프로젝트에서 NuGet 패키지가 할 수 있는 일 제한하기

  • 저자: Gérald Barré
  • 태그: dotnet #nuget #security

주요 내용

  • NuGet 패키지가 라이브러리 외에 MSBuild props/targets와 Roslyn 분석기를 포함해 복원·빌드 시점에 코드를 실행할 수 있다는 공급망 위험
  • IncludeAssets="compile;runtime"으로 분석기와 빌드 타겟을 제외하는 기본 완화 방법
  • ExcludeAssets="analyzers;build;buildMultitargeting;buildTransitive"로 특정 자산만 선택적으로 제외
  • 런타임 전용·빌드 도구 전용 등 시나리오별 설정과 PrivateAssets="all" 조합
  • 일부 패키지는 분석기·빌드 타겟이 정상 동작에 필수라 설정 변경 후 테스트가 필요하다는 주의사항

:bookmark_tabs: 가벼운 읽을거리

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


Testcontainers — 일회용 Docker 컨테이너 속 실제 의존성

  • docker-compose 대비 수명주기 자동 정리·준비 상태 감지·동적 포트의 이점과 xUnit IAsyncLifetime 기반 DatabaseFixture 구현
  • HTTP 헬스체크·로그 메시지 기반 대기 전략, Ryuk 정리 컨테이너, 고정 지연·포트 하드코딩 등 안티패턴 정리

.NET OSS 재라이선싱 논란은 인센티브 문제다

  • IdentityServer 상용화 성공 이후 유지보수자들의 재라이선싱 흐름과, .NET Foundation이 소속 프로젝트의 재라이선싱을 막을 권한이 없다는 구조적 한계
  • OSMF(소스 유지, 바이너리 배포에만 수수료)와 완전한 재라이선싱(MIT→BSL)의 구분, 기부가 아닌 계약 기반 고객 관계로 전환하자는 제안

Blazor를 위한 보안 헤더와 CSP — 미들웨어 하나로 모든 호스트에

  • Blazor는 'wasm-unsafe-eval'이 필요해 CSP를 잘못 조이면 앱이 하얗게 로드되며 부팅에 실패하는 문제
  • ICspPolicyProvider로 CSP를 런타임 해석하고, API origin 파싱 실패 시 강제 차단 대신 Report-Only로 강등해 설정 오류가 장애로 번지지 않게 하는 구성

DocumentDB Orleans 스택

  • Orleans의 클러스터 멤버십·그레인 스토리지·리마인더·그레인 디렉토리·스트림 5개 지속성 계층을 단일 IDocumentStore 추상화로 통합
  • ETag 기반 CAS로 동시성을 제어하고, 트랜잭션 내 행 수준 잠금으로 스트림 시퀀스 번호를 할당해 삽입 순서와 커밋 순서를 일치시키는 구현

패키지된 애플리케이션의 유형 (MSIX)

  • 패키지된 프로세스를 RuntimeBehavior 속성 기준으로 Universal(UWP), Centennial(Desktop Bridge), Win32alacarte 세 유형으로 분류
  • Centennial은 보안·정리를 목표로 Windows 10 RS1에, Win32alacarte는 호환성을 최우선으로 20H1에 도입된 설계 배경

쓰레기 코드, 깨끗한 메모리 — .NET 가비지 컬렉터 이해하기

  • Gen 0/1/2 세대별 수집과 SOH·LOH(85KB 이상)·POH·FOH 힙 영역, 표시-계획-재배치-압축 단계
  • ArrayPool<T>, string.Create, ref readonly 등 할당 감소 패턴과 dotnet-gcdump·PerfView 진단 도구, .NET 10의 DATAS와 JIT 이스케이프 분석

Aspire 13.5

  • Kubernetes 영구 볼륨을 C#/TypeScript로 모델링(스토리지 클래스·용량·접근 모드 설정, PVC 자동 생성)하고 WithTerminal()로 대시보드 내 인터랙티브 터미널 세션 지원
  • AsExistingInResourceGroup 등으로 기존 Azure 리소스를 스코프별로 참조, 실험 API 표기와 aspire update --migrate 마이그레이션 경로 명시

Windows Package Manager v1.29.290

  • 실험 기능 Source Priority로 소스별 우선순위를 지정해 모호성 발생 시 프롬프트 대신 최우선 소스를 자동 선택
  • export 시 설치에 쓴 --override/--custom 인자를 캡처해 import에서 재적용, 모든 명령에 --no-progress 플래그 추가

SDK 없이 .NET 도구 실행하기

  • .NET 10 dnx가 캐시된 도구 실행에도 SDK 오버헤드를 요구하는 문제를 Native AOT로 빌드한 ndx로 해결, 명령 문법은 그대로 유지
  • 콜드 캐시 실행 기준 dnx 580ms 대 ndx 10ms, NuGet 캐시를 공유하며 .netconfig로 플로팅 버전 자동 업데이트
3개의 좋아요