웹 개발 데일리 브리핑 — 2026-11-10
Soshy··조회수 —

기준 시점: 2026-11-10 06:00 KST
조사 범위: 전일 2026-11-09 06:00 ~ 당일 2026-11-10 06:00 KST
추천 글 범위: 기준 시점 당시 최근 7일
1️⃣ 프론트엔드
Astro 7.4.8 및 개발 도구 업데이트 — CLI 인자 처리, VS Code Import 정리, TypeScript 자동 완성 수정
- 발표일: 2026-11-09
- GitHub 릴리스 표시 시각: 2026-11-09 14:49 — 표시 시간대 미확인
- 버전: Astro 7.4.8, astro-vscode 2.17.3, @astrojs/ts-plugin 1.11.0, @astrojs/react 7.1.5
- 안정화 단계: 정식 안정 버전 및 개발 도구 업데이트
- 적용 대상: Astro CLI, VS Code Extension, TypeScript Language Service, React Integration
- 원문: https://github.com/withastro/astro/releases/tag/astro%407.4.8
- 개발 도구 릴리스: https://github.com/withastro/astro/releases
Astro가 프레임워크와 관련 개발 도구의 새로운 버전을 공개했다.
이번 업데이트는 새로운 렌더링 방식이나 대규모 사용자 API를 도입하는 릴리스가 아니다. 주요 변경은 CLI의 인자 처리와 편집기에서 발생하던 자동 완성 및 Import 정리 문제를 수정하는 데 집중돼 있다.
특히 하나의 .astro 파일에 Frontmatter와 클라이언트 Script가 함께 존재하는 경우의 개발 경험이 개선됐다.
Astro CLI의 인자 처리 변경
Astro 7.4.8에는 다음 변경이 포함됐다.
Updates CLI argument parsing
CLI(Command Line Interface)는 터미널에서 프로그램을 실행할 때 사용하는 명령줄 인터페이스다.
예를 들어 Astro에서는 다음과 같은 명령을 사용할 수 있다.
astro devastro buildastro previewastro check
이러한 명령은 옵션과 인자를 전달받을 수 있다.
CLI Parser는 사용자가 입력한 문자열을 해석해 어떤 명령을 실행하고 어떤 옵션을 적용할지 결정한다.
이번 패치는 이러한 인자 처리 로직을 갱신했다.
다만 공식 릴리스 노트에는 특정 인자 조합에서 발생하던 오류의 구체적인 재현 조건이나 새로운 옵션의 추가가 별도로 설명되지 않았다.
따라서 새로운 CLI 기능이 추가됐다고 단정할 수 없다.
이번 변경은 기존 명령 처리의 유지보수 업데이트로 구분하는 것이 적절하다.
VS Code에서 Import가 일부만 정리되던 문제
실제 개발 경험에 더 직접적인 변화는 astro-vscode 2.17.3에 포함됐다.
Astro 컴포넌트는 일반적인 React 또는 Vue 컴포넌트 파일과 다른 구조를 가질 수 있다.
대표적으로 다음 영역이 존재한다.
- Frontmatter
- HTML Template
<script>태그<style>태그
Frontmatter에는 서버 측에서 사용하는 JavaScript나 TypeScript를 작성할 수 있다.
반면 <script>에는 브라우저에서 실행하는 JavaScript가 포함될 수 있다.
예를 들어 하나의 .astro 파일에 다음 두 종류의 Import가 존재할 수 있다.
- Frontmatter에서 가져오는 서버용 Module
<script>에서 가져오는 클라이언트 Module
개발자는 VS Code의 Organize Imports 기능을 이용해 Import를 정리할 수 있다.
대표적인 Code Action은 다음과 같다.
source.organizeImports
문제는 두 영역이 함께 존재하는 경우였다.
기존 Extension에서는 Import 정리 작업이 두 영역 가운데 하나에서만 실행될 수 있었다.
따라서 개발자가 Organize Imports를 실행해도 일부 Import는 정리되지 않은 상태로 남을 수 있었다.
이번 업데이트는 이러한 문제를 수정한다.
중요한 점은 Astro 파일이 단순한 JavaScript 파일 하나가 아니라 서로 다른 실행 영역을 포함하는 복합 문서라는 사실이다.
따라서 편집기에서도 각 영역을 올바르게 구분해 Code Action을 적용해야 한다.
TypeScript 자동 Import의 컴포넌트 이름 수정
@astrojs/ts-plugin 1.11.0에는 자동 Import와 관련된 수정도 포함됐다.
Astro의 TypeScript 도구는 .astro 컴포넌트를 내부적으로 TypeScript가 분석할 수 있는 형태로 변환할 수 있다.
이 과정에서는 실제 Astro 파일과 별도의 생성된 TSX 표현이 사용될 수 있다.
문제는 자동 Import 제안에서 발생했다.
예를 들어 개발자가 다른 Astro 컴포넌트를 가져오려고 한다고 가정해 보자.
편집기는 실제 컴포넌트 이름을 제안해야 한다.
하지만 기존 구현에서는 내부적으로 생성한 TSX 컴포넌트 이름을 제안할 수 있었다.
이번 수정은 실제 .astro 파일의 컴포넌트 이름을 기준으로 자동 Import를 제안하도록 변경했다.
이렇게 하면 개발자가 내부 변환 결과를 직접 알 필요 없이 실제 소스 파일의 이름을 기준으로 작업할 수 있다.
Node.js 최소 버전 변경
이번 TypeScript Plugin 업데이트에는 중요한 호환성 변경도 존재한다.
새로운 Node.js 요구사항은 다음과 같다.
^20.19.0 || >=22.12.0
즉 Node.js 20을 사용하는 경우에는 최소 20.19.0 이상이 필요하다.
Node.js 22를 사용하는 경우에는 최소 22.12.0 이상이 필요하다.
Node.js 21처럼 지원 범위에 포함되지 않는 버전도 존재할 수 있다.
이는 @astrojs/ts-plugin의 요구사항이다.
따라서 Astro 프레임워크 전체의 Node.js 지원 범위가 이번 패치에서 동일하게 변경됐다고 해석해서는 안 된다.
실제로 TypeScript Plugin을 직접 사용하는 개발 도구 환경에서는 해당 요구사항을 확인해야 한다.
React Integration의 Peer Dependency 확대
@astrojs/react 7.1.5에서는 다음 패키지의 Peer Dependency 범위가 변경됐다.
oxc-transform-react
기존 범위는 다음과 같았다.
^0.145.0
새로운 범위는 다음과 같다.
>=0.145.0 <0.157.0
이는 지원 가능한 패키지 버전 범위를 확대한 것이다.
실제로 React 컴포넌트를 변환하는 새로운 실행 알고리즘이 추가됐다는 의미는 아니다.
다만 기존에 버전 범위 문제로 발생할 수 있던 의존성 호환성 오류를 줄이는 데 도움이 될 수 있다.
적용 범위와 제한사항
이번 네 릴리스는 다음처럼 구분할 수 있다.
| 구성요소 | 변경 내용 |
|---|---|
| Astro 7.4.8 | CLI 인자 처리 갱신 |
| astro-vscode 2.17.3 | Frontmatter와 Script의 Import 정리 수정 |
| @astrojs/ts-plugin 1.11.0 | 자동 Import 이름 수정 및 Node.js 요구사항 변경 |
| @astrojs/react 7.1.5 | oxc-transform-react 지원 버전 범위 확대 |
이번 발표에는 개발 서버의 시작 시간이나 빌드 속도, TypeScript 검사 성능에 관한 정량적인 Benchmark가 포함되지 않았다.
따라서 모든 Astro 프로젝트에서 성능이 개선됐다고 판단할 수 없다.
Astro의 실제 개발 경험에서 발생하던 편집기와 TypeScript 통합 문제를 수정하고 의존성 호환성을 조정한 정식 업데이트다.
이번 조사 범위에서 이외에 포함할 만한 주요 신규 프론트엔드 발표는 확인하지 못했습니다.
React, Next.js, Vue, Nuxt, Angular, Svelte, SvelteKit, Remix의 공식 블로그와 릴리스 채널을 검토했다.
Next.js 16.5 계열의 Canary 업데이트도 확인했지만, 이번 기간에 확인된 변경은 문서 및 Turbopack의 내부 유지보수 성격이 강해 중요한 신규 기능 발표로 선정하지 않았다.
이전 브리핑에서 소개한 Next.js의 실험적 Navigation API, Vue 3.5.45, Astro 7.4.6·7.4.7도 반복하지 않았다.
2️⃣ 웹 플랫폼·브라우저
이번 조사 범위에서 포함할 만한 주요 신규 발표를 확인하지 못했습니다.
W3C, WHATWG, TC39, MDN, Chrome Developers·Chromium, Mozilla·Firefox, WebKit·Safari의 공식 공개 정보와 JavaScript·TypeScript·CSS, 웹 성능 및 접근성 관련 발표를 확인했다.
이번 기간에 새로운 주요 웹 표준이 정식 채택됐거나 브라우저의 안정 버전에서 중요한 신규 API가 출시됐다는 발표는 확인하지 못했다.
WHATWG의 Living Standard는 지속적으로 변경될 수 있다. 하지만 문서의 갱신 날짜만으로 새로운 브라우저 기능의 출시를 판단할 수는 없다.
이번 조사에서는 실제 규격 변경과 브라우저 구현 상태를 함께 검증할 수 있는 주요 신규 항목을 확인하지 못해 포함하지 않았다.
최근 소개한 Chrome 158 Beta, Firefox 159 및 W3C Geolocation 규격 개정도 반복하지 않았다.
이번 추천 글에서는 이미 사용할 수 있는 CSS 기능을 실제 브라우저에서 구현할 때 나타나는 호환성 문제를 자세히 다룬다.
특히 @starting-style을 이용한 Popover 진입 애니메이션은 주요 브라우저에서 사용할 수 있지만, 종료 애니메이션에서는 display와 overlay의 지원 범위가 다르다는 점을 실제 테스트로 확인한 자료를 선정했다.
3️⃣ 백엔드·인프라
AWS Nitro Sandboxes 정식 출시 — EKS Pod·ECS Task·EC2 작업을 하드웨어 격리된 VM에서 실행
- 발표일: 2026-11-09 — 정확한 게시 시각 미확인
- 발표 주체: AWS
- 안정화 단계: 정식 출시
- 적용 대상: Amazon EKS, Amazon ECS, Amazon EC2
- 지원 환경: 대부분의 Nitro 기반 EC2 인스턴스, 전 세계 AWS 상용 리전
- 추가 서비스 요금: 없음. 기존 EC2 리소스 사용 비용은 별도
- 원문: https://aws.amazon.com/about-aws/whats-new/2026/11/nitro-sandboxes-eks-ecs-ec2/
AWS가 컨테이너 및 애플리케이션 작업을 가벼운 하드웨어 격리 가상 머신에서 실행할 수 있도록 하는 Nitro Sandboxes를 출시했다.
이번 발표는 일반적인 컨테이너 실행 기능을 하나 더 추가한 것이 아니다.
특히 신뢰할 수 없는 코드나 여러 고객의 코드를 같은 서버에서 실행해야 하는 환경에서 보안 경계를 강화하도록 설계됐다.
대표적인 사용 사례는 AI 코딩 에이전트다.
일반적인 컨테이너 격리의 한계
예를 들어 사용자가 웹사이트에서 Python 코드를 제출하면 서버가 해당 코드를 실행하는 서비스를 운영한다고 가정해 보자.
서버는 사용자별로 Docker Container를 생성할 수 있다.
컨테이너를 사용하면 파일시스템과 프로세스 등의 실행 환경을 어느 정도 분리할 수 있다.
하지만 일반적인 컨테이너는 호스트 운영체제의 Kernel을 공유한다.
따라서 컨테이너의 격리 정책이나 Kernel에 보안 문제가 존재한다면 다른 작업에 영향을 줄 가능성이 있다.
특히 사용자가 제출한 코드를 직접 실행하는 서비스에서는 이러한 위험을 고려해야 한다.
예를 들어 다음과 같은 서비스다.
- 온라인 코드 실행기
- AI 코딩 에이전트
- 자동화된 테스트 서비스
- 여러 고객이 사용하는 플러그인 실행 환경
- 외부에서 업로드한 프로그램 분석
Nitro Sandboxes는 이러한 작업을 별도의 VM 격리 경계 안에서 실행하도록 한다.
Pod 또는 Task별 하드웨어 격리
새로운 구조에서는 다음 실행 단위를 각각 별도의 가상 머신으로 격리할 수 있다.
- EKS Pod
- ECS Task
- EC2에서 직접 생성한 경량 VM
예를 들어 하나의 EC2 인스턴스에서 여러 AI 에이전트가 실행된다고 가정해 보자.
각 에이전트는 서로 다른 사용자의 프로젝트 파일을 처리할 수 있다.
이때 하나의 에이전트가 실행하는 코드가 다른 에이전트의 파일이나 실행 환경에 접근해서는 안 된다.
Nitro Sandboxes는 각 작업에 Nitro Hypervisor 기반의 VM 격리 경계를 제공한다.
따라서 하나의 EC2 인스턴스를 여러 작업이 공유하면서도 VM 수준의 격리를 사용할 수 있다.
EKS·ECS 통합
AWS는 EKS 및 ECS에서 Nitro Sandboxes를 간단한 설정 변경으로 사용할 수 있도록 제공한다.
공식 발표에서는 관련 기능을 활성화하는 작업이 한 줄의 설정 변경으로 가능하다고 설명한다.
하지만 실제로 격리된 애플리케이션을 안전하게 운영하려면 다음 사항도 고려해야 한다.
- 컨테이너 이미지의 권한
- 네트워크 접근 정책
- 파일 및 저장소 접근 범위
- 인증 정보 전달
- 로그와 모니터링
- 작업별 CPU 및 메모리 제한
Nitro Sandboxes는 기존 AWS 서비스와도 통합된다.
대표적으로 다음 기능을 지원한다.
- EKS Pod Identity
- VPC CNI
- EBS
- EFS
- S3
- CloudWatch
따라서 격리된 VM을 사용한다고 해서 기존 Kubernetes 및 AWS의 운영 기능을 모두 새롭게 구현해야 하는 것은 아니다.
Attestation과 Secret 전달
이번 발표에서 중요한 보안 기능은 Attestation이다.
Attestation은 실제 실행 환경이 기대한 구성과 무결성 조건을 충족하는지 검증하는 과정이다.
예를 들어 사용자가 제출한 코드를 실행하는 Sandbox가 있다고 가정해 보자.
해당 Sandbox에는 외부 API에 접근하기 위한 Secret이 필요할 수 있다.
하지만 실행 환경의 무결성이 검증되지 않았다면 Secret을 전달하는 것이 위험할 수 있다.
Nitro Sandboxes는 암호학적인 Attestation을 지원한다.
따라서 검증된 Sandbox에만 Secret을 제공하는 구조를 만들 수 있다.
하지만 Attestation이 존재한다는 사실만으로 Sandbox 안에서 실행되는 모든 코드가 안전해지는 것은 아니다.
실제로 어떤 환경을 신뢰할지 결정하고 Secret의 접근 범위를 제한하는 정책이 필요하다.
또한 신뢰할 수 없는 코드가 허용된 네트워크나 파일에 접근하는 문제는 별도로 고려해야 한다.
EC2에서 직접 실행
EKS나 ECS를 사용하지 않는 경우에도 Nitro Sandboxes를 사용할 수 있다.
AWS는 EC2에서 직접 실행하는 방식을 제공한다.
이 경우 다음 구성요소를 직접 관리할 수 있다.
- AMI
- 가상 머신 실행 방식
- Orchestration
- VMM(Virtual Machine Monitor)
Amazon Linux와 Firecracker가 지원된다.
Firecracker는 가벼운 MicroVM을 실행하기 위한 기술이다.
이러한 구조는 Kubernetes나 ECS의 실행 모델에 맞지 않는 사용자 정의 Sandbox 시스템을 구축할 때 활용할 수 있다.
하지만 직접 실행하는 경우에는 관리형 통합이 제공하는 운영 기능 가운데 일부를 직접 구성해야 할 수 있다.
성능과 비용의 제한사항
AWS는 Nitro Sandboxes가 기존 EC2에 가까운 성능을 제공한다고 설명한다.
하지만 이번 공식 발표에는 다음 수치가 포함되지 않았다.
- MicroVM 시작 시간의 P50 및 P99
- 일반 컨테이너와 비교한 CPU 오버헤드
- 메모리 추가 사용량
- 실제 웹 요청 처리량
- AI 코딩 작업의 완료 시간
따라서 'Near-native Performance'라는 표현을 독립적으로 검증된 성능 수치로 취급해서는 안 된다.
또한 Nitro Sandboxes 자체에는 추가 요금이 없지만 실제 작업은 EC2 인스턴스의 CPU와 메모리를 사용한다.
따라서 별도의 서비스 요금이 없다는 사실이 실행 비용이 없다는 의미는 아니다.
이번 발표는 여러 고객이나 AI 에이전트의 신뢰할 수 없는 코드를 실행하면서 VM 수준의 격리를 제공할 수 있도록 EKS·ECS·EC2의 실행 환경을 확장한 인프라 기능이다.
AWS EKS Auto Mode·Karpenter, Dynamic Resource Allocation 지원 — GPU와 EFA를 작업별 요구사항에 따라 할당
- 발표일: 2026-11-09 — 정확한 게시 시각 미확인
- 발표 주체: AWS
- 안정화 단계: 정식 기능 지원
- 적용 대상: Amazon EKS Auto Mode, Karpenter
- 지원 리소스: NVIDIA GPU, Elastic Fabric Adapter(EFA)
- 버전 제한: EKS Auto Mode는 Kubernetes 1.36 이상, Karpenter는 지원하는 모든 Kubernetes 버전
- 원문: https://aws.amazon.com/about-aws/whats-new/2026/11/amazon-eks-auto-mode-karpenter-dra/
AWS가 EKS Auto Mode와 Karpenter에서 Kubernetes의 Dynamic Resource Allocation(DRA)을 지원하도록 변경했다.
이번 기능은 주로 GPU와 고성능 네트워크 장치를 사용하는 애플리케이션에 영향을 준다.
일반적인 웹 API에서는 CPU와 메모리를 기준으로 Pod를 배치하는 경우가 많다.
반면 AI 모델의 학습이나 추론에서는 특정 GPU 모델과 연결 구조가 중요할 수 있다.
예를 들어 하나의 작업이 특정 NVIDIA GPU와 EFA 네트워크 장치를 함께 필요로 할 수 있다.
이 경우 단순히 GPU가 하나 이상 존재하는 노드에 작업을 배치하는 것으로는 충분하지 않을 수 있다.
DRA의 역할
DRA는 Kubernetes에서 특수한 장치와 리소스를 더 유연하게 할당하기 위한 기능이다.
기존에는 Device Plugin을 이용해 GPU 등을 사용할 수 있었다.
하지만 GPU의 종류와 특성, 다른 장치와의 연결 관계까지 고려하려면 더 상세한 정보가 필요할 수 있다.
DRA에서는 다음 리소스를 사용할 수 있다.
ResourceClaimResourceClaimTemplate
이러한 리소스는 실제 작업에 필요한 장치의 조건을 표현한다.
예를 들어 다음 조건을 지정할 수 있다.
- GPU 모델
- GPU Architecture
- 장치의 속성
- GPU와 네트워크 장치의 연결 관계
Kubernetes는 이러한 요구사항에 맞는 장치를 할당하도록 구성할 수 있다.
GPU와 EFA의 연결 관계
분산 AI 학습에서는 여러 GPU가 데이터를 주고받아야 할 수 있다.
이때 네트워크 장치의 성능도 중요하다.
EFA는 AWS의 고성능 네트워크 인터페이스다.
이번 DRA 지원에서는 GPU와 EFA의 PCIe 연결 관계를 고려해 장치를 배치할 수 있다.
예를 들어 특정 GPU와 네트워크 장치가 적절한 Topology에 존재하도록 요청할 수 있다.
이러한 구성은 불필요한 데이터 전송 경로를 줄이는 데 도움이 될 수 있다.
하지만 이번 발표에는 분산 학습 시간이 몇 퍼센트 단축되는지에 관한 Benchmark가 포함되지 않았다.
따라서 실제 학습 성능이 일정한 비율로 개선된다고 단정할 수 없다.
Karpenter의 노드 자동 생성
Karpenter는 Kubernetes 작업의 요구사항에 따라 EC2 인스턴스를 자동으로 생성할 수 있다.
새로운 DRA 지원에서는 장치 요구사항도 고려한다.
예를 들어 작업이 특정 GPU 모델을 요구한다고 가정해 보자.
Karpenter는 해당 조건을 충족하는 EC2 인스턴스를 선택할 수 있다.
AWS는 적합한 조건을 충족하면서 비용이 낮은 인스턴스를 선택하도록 동작한다고 설명한다.
하지만 실제 비용은 사용 가능한 인스턴스와 리전, 용량 및 작업의 요구사항에 영향을 받는다.
따라서 모든 GPU 작업의 비용이 반드시 감소하는 것은 아니다.
Device Plugin과의 혼합 사용
이번 업데이트에는 마이그레이션 관련 기능도 포함됐다.
기존 Kubernetes 환경에서는 GPU Device Plugin을 사용하는 Pod가 존재할 수 있다.
새로운 DRA를 사용하는 Pod도 같은 클러스터에서 실행될 수 있다.
문제는 두 방식이 동일한 장치를 중복으로 할당하려고 시도하는 경우다.
AWS는 EKS Auto Mode에서 같은 종류의 장치에 대해 DRA와 Device Plugin을 사용하는 작업이 동일한 노드에 배치되지 않도록 처리한다.
이렇게 하면 장치가 실제보다 많이 할당되는 Oversubscription 위험을 줄일 수 있다.
하지만 이 동작이 모든 GPU Sharing 문제를 자동으로 해결한다는 의미는 아니다.
실제 GPU를 어떻게 공유할 수 있는지는 장치와 Driver의 지원 범위에 따라 달라진다.
지원 범위
DRA는 다음 환경에서 사용할 수 있다.
- EKS Auto Mode: Kubernetes 1.36 이상
- Karpenter: 지원하는 모든 Kubernetes 버전
리전 제한도 존재한다.
다음 리전은 이번 지원 대상에서 제외된다.
- AWS GovCloud(US)
- 중국 리전
이번 발표는 Kubernetes 작업이 단순히 GPU 개수뿐 아니라 실제 장치의 특성과 연결 관계를 요구할 수 있도록 하고, 해당 조건에 맞는 인프라를 자동으로 구성하는 기능이다.
Cloudflare Durable Objects, 비활성 객체의 재시작 위치 개선 — 기존 서버의 저장 데이터 재사용으로 Cold Start 감소
- 발표일: 2026-11-09 — 정확한 게시 시각 미확인
- 발표 주체: Cloudflare
- 안정화 단계: 정식 플랫폼 최적화
- 적용 대상: Cloudflare Durable Objects
- 배포 범위: 모든 Cloudflare 리전
- 원문: https://developers.cloudflare.com/changelog/post/2026-11-09-durable-objects-reactivation-placement/
Cloudflare가 비활성 상태에 들어갔던 Durable Object를 다시 시작할 때 이전에 실행했던 서버를 우선 선택하도록 배치 방식을 개선했다.
이번 업데이트는 새로운 Durable Object API를 추가하는 기능이 아니다.
실제 실행 환경의 선택 방식을 변경해 초기화에 필요한 작업을 줄이려는 플랫폼 최적화다.
Durable Object의 실행 위치
Durable Object는 특정 ID와 연결된 상태를 관리할 수 있는 서버리스 실행 단위다.
예를 들어 하나의 채팅방을 하나의 Durable Object로 관리할 수 있다.
해당 객체는 사용자의 메시지와 연결 상태를 처리한다.
하지만 사용자가 오랫동안 요청하지 않는 경우에는 실행 인스턴스가 비활성 상태에 들어갈 수 있다.
이후 새로운 요청이 도착하면 객체를 다시 활성화해야 한다.
문제는 어떤 서버에서 다시 시작하는지다.
기존 실행 환경과 다른 서버에서 시작하면 필요한 상태와 데이터를 새롭게 준비해야 할 수 있다.
SQLite 기반 객체의 초기화 비용
Durable Objects는 SQLite 기반 저장소를 사용할 수 있다.
실제 작업을 수행하려면 관련 데이터에 접근할 수 있어야 한다.
이때 데이터가 이전 서버의 Cache에 남아 있다면 더 빠르게 접근할 가능성이 있다.
반대로 다른 서버에서 시작한다면 필요한 데이터를 다시 가져오는 작업이 발생할 수 있다.
Cloudflare는 이번 업데이트에서 가능한 경우 마지막으로 해당 객체를 실행했던 서버에서 다시 활성화하도록 변경했다.
이렇게 하면 이전 실행 환경에 남아 있는 데이터를 재사용할 수 있다.
특히 비활성 상태에 들어간 뒤 짧은 시간 안에 다시 활성화되는 SQLite 기반 Durable Object에서 도움이 될 수 있다.
실제 서비스에서의 적용 예
예를 들어 실시간 협업 서비스를 생각할 수 있다.
사용자는 특정 문서를 열어 편집한다.
이후 모든 사용자가 문서를 닫는다.
해당 문서의 Durable Object는 비활성화될 수 있다.
잠시 뒤 다른 사용자가 같은 문서를 연다.
이때 기존 서버에 관련 데이터가 남아 있다면 새로운 서버에서 모든 데이터를 다시 준비하는 작업을 줄일 수 있다.
하지만 이러한 이점은 실제 트래픽 패턴에 영향을 받는다.
예를 들어 매우 오랜 시간이 지난 뒤 객체가 다시 활성화된다면 이전 서버의 관련 데이터가 더 이상 남아 있지 않을 수 있다.
또한 실제 실행 서버의 상태와 용량에 따라 다른 서버에서 실행될 수 있다.
Best-effort 동작
Cloudflare는 이번 배치 최적화를 가능한 경우에 적용한다고 설명한다.
따라서 다음 동작을 보장하지 않는다.
- 모든 Durable Object가 항상 이전 서버에서 시작
- 모든 Cold Start가 제거됨
- 실행 지연이 특정 비율만큼 감소
- 하나의 객체가 영구적으로 특정 물리 서버에 고정됨
이번 발표에는 실제 P50 및 P99 Cold Start 시간이 얼마나 감소했는지에 관한 정량적인 Benchmark도 포함되지 않았다.
따라서 모든 애플리케이션의 초기 응답 시간이 개선된다고 단정해서는 안 된다.
기존 API와의 호환성
이번 변경은 모든 리전에 적용됐다.
개발자가 별도의 Compatibility Flag를 활성화할 필요는 없다.
또한 Durable Object의 기존 API를 변경할 필요도 없다.
하지만 기존 서버에서 재시작할 가능성이 높아졌다는 사실만으로 메모리 상태가 영구적으로 유지되는 것은 아니다.
Durable Object의 실제 실행 환경은 여전히 종료되거나 재시작될 수 있다.
따라서 반드시 보존해야 하는 데이터는 Durable Storage에 저장해야 한다.
이번 업데이트는 Durable Object가 다시 활성화될 때 이전 실행 서버의 데이터를 재사용할 가능성을 높여 불필요한 초기화 비용을 줄이도록 한 플랫폼 최적화다.
Cloudflare Markdown for Agents, 변환 성공률·실패 원인·Token 절감 추정치 분석 지원
- 발표일: 2026-11-09 — 정확한 게시 시각 미확인
- 발표 주체: Cloudflare
- 안정화 단계: 정식 기능
- 적용 대상: Markdown for Agents, Cloudflare Analytics, GraphQL API, Traces
- 신규 분석 항목: 변환 결과, 실패 원인, 입력·출력 크기, CPU 시간, Token 절감 추정
- 원문: https://developers.cloudflare.com/changelog/post/2026-11-09-markdown-for-agents-observability/
Cloudflare가 웹페이지를 AI 에이전트가 읽기 쉬운 Markdown으로 변환하는 기능의 처리 결과를 분석할 수 있도록 Observability를 확장했다.
이번 발표는 Markdown for Agents를 처음 출시한 소식이 아니다.
기존 기능에 Analytics와 Logs, Playground 및 Trace 정보를 추가한 것이다.
특히 변환에 실패하거나 일부 콘텐츠만 처리된 페이지를 구분할 수 있게 됐다는 점이 중요하다.
HTML과 Markdown의 차이
일반적인 웹페이지는 HTML을 사용한다.
HTML에는 다음 정보가 포함될 수 있다.
- 문서의 실제 본문
- Navigation
- Footer
- Button
- 광고
- Script
- 스타일 관련 요소
사람이 브라우저에서 페이지를 보는 경우 이러한 구조가 필요하다.
하지만 AI 에이전트가 기술 문서의 내용을 읽으려는 상황에서는 일부 정보가 불필요할 수 있다.
예를 들어 사용자가 AI 에이전트에게 다음과 같이 요청했다고 가정해 보자.
이 API의 인증 방식을 정리해 줘.
에이전트는 해당 기술 문서의 실제 내용을 읽어야 한다.
하지만 전체 HTML을 처리하면 Navigation과 Script 등 본문과 관련 없는 데이터도 함께 가져올 수 있다.
Markdown for Agents는 이러한 콘텐츠를 Markdown 형태로 제공하도록 한다.
하지만 변환이 항상 성공하는 것은 아니다.
복잡한 HTML과 동적인 콘텐츠에서는 일부 정보가 누락되거나 변환에 실패할 수 있다.
새로운 Analytics
이번 업데이트에서는 Dashboard에 새로운 분석 화면이 추가됐다.
경로는 다음과 같다.
Agent Readiness > Markdown for Agents
여기에서는 다음 정보를 확인할 수 있다.
- 전체 변환 횟수
- 정상적으로 완료된 변환
- 일부만 완료된 변환
- 실패한 변환
- Token 절감 추정치
- 자주 변환된 페이지
- 주요 User-Agent
- 변환 실패 원인
- 콘텐츠 크기 제한에 걸린 페이지
예를 들어 기술 문서 사이트를 운영한다고 가정해 보자.
특정 API 문서는 Markdown으로 정상 변환된다.
하지만 다른 문서는 표와 복잡한 HTML 때문에 일부 내용만 처리될 수 있다.
새로운 Analytics에서는 이러한 차이를 확인할 수 있다.
따라서 Markdown 변환을 활성화했다는 사실만으로 모든 페이지가 정상적으로 처리됐다고 가정할 필요가 없다.
Playground
이번 업데이트에는 Playground도 포함됐다.
관리자는 실제 URL을 지정해 해당 페이지를 Markdown으로 변환할 수 있다.
그리고 다음 정보를 비교할 수 있다.
- 원본 HTML
- 변환된 Markdown
- HTTP Response Header
- 예상 Token 절감량
예를 들어 Markdown 결과에서 실제 코드 블록이나 표가 유지되는지 확인할 수 있다.
또한 원래 본문에 존재하던 중요한 내용이 누락됐는지 직접 검토할 수 있다.
하지만 Playground에서 하나의 페이지가 정상적으로 변환됐다는 사실이 사이트 전체의 모든 페이지에 같은 결과를 보장하는 것은 아니다.
복잡한 페이지 구조와 콘텐츠 크기에 따라 결과가 달라질 수 있기 때문이다.
GraphQL Analytics API
이번 분석 정보는 GraphQL API에서도 사용할 수 있다.
대표적인 필드는 다음과 같다.
markdownForAgentsAdaptiveGroups
이 API는 Zone 및 Account 범위에서 사용할 수 있다.
다음 정보를 조회할 수 있다.
- 변환 횟수
- 결과 상태
- 예상 Token 절감량
- 페이지별 오류
- 최근 7일의 분석 정보
따라서 Dashboard에 직접 접속하지 않고도 내부 모니터링 시스템에서 변환 상태를 확인할 수 있다.
예를 들어 특정 페이지에서 실패율이 증가하는지 분석할 수 있다.
하지만 API가 반환하는 값의 의미를 정확히 구분해야 한다.
Token 절감 수치의 한계
Cloudflare는 Token 절감량을 추정한다.
하지만 이 값은 실제 AI 모델의 Tokenizer로 계산한 정확한 Token 수가 아니다.
공식 문서는 입력과 출력의 Byte 크기를 이용한 추정치라고 설명한다.
또한 전체 변환에 성공한 결과를 기준으로 계산한다.
따라서 다음 두 값은 서로 다르다.
- Cloudflare가 추정한 Token 감소량
- 실제 특정 AI 모델에 입력했을 때 감소한 Token 수
예를 들어 같은 Markdown 문서라도 사용하는 모델의 Tokenizer에 따라 실제 Token 수가 달라질 수 있다.
특히 한국어와 영어, 코드 블록의 비중에 따라 차이가 발생할 수 있다.
또한 GraphQL의 일부 집계는 Sampling을 이용해 추정한다.
따라서 표시된 값이 실제 모든 요청을 하나씩 세어 얻은 정확한 값이라고 판단해서는 안 된다.
Traces와 CPU 시간
Cloudflare Traces에서는 새로운 Span을 확인할 수 있다.
markdown_conversion
이 Span에는 다음 정보가 포함될 수 있다.
- 변환 성공 여부
- 입력 Byte 수
- 출력 Byte 수
- 변환에 사용한 CPU 시간
- 콘텐츠 제한 여부
이러한 정보는 실제 웹 요청의 다른 처리 과정과 Markdown 변환 비용을 비교하는 데 사용할 수 있다.
예를 들어 특정 페이지의 Markdown 변환이 느리다고 가정해 보자.
Trace를 통해 실제 변환 처리에 CPU 시간이 많이 사용됐는지 확인할 수 있다.
하지만 이번 발표에는 전체 요청 지연 시간이 몇 퍼센트 증가하거나 감소하는지에 관한 Benchmark가 포함되지 않았다.
이번 업데이트는 AI 에이전트에 제공하는 Markdown 콘텐츠의 생성 결과와 처리 비용을 실제 운영 환경에서 분석할 수 있도록 Observability를 확장한 기능이다.
Cloudflare Agents SDK, Codex 실행 루프를 Durable Object로 옮기는 실험적 Harness 공개
- 발표일: 2026-11-09 — 정확한 게시 시각 미확인
- 발표 주체: Cloudflare
- 안정화 단계: Experimental Example
- 적용 대상: Cloudflare Agents SDK, Durable Objects, WebAssembly
- 관련 기능: Tasks, Sessions, Streams, Workspace
- 원문: https://developers.cloudflare.com/changelog/post/2026-11-09-codex-harness/
Cloudflare가 Codex의 에이전트 실행 루프를 Durable Object 안에서 실행하는 실험적 예제를 공개했다.
이번 발표는 새로운 정식 AI 코딩 에이전트 제품의 출시가 아니다.
기존 Codex 실행 구조를 Cloudflare의 서버리스 환경에 연결하는 방법을 보여주는 참고 구현이다.
특히 Rust로 작성된 에이전트의 실행 루프를 WebAssembly로 변환해 Durable Object에서 실행한다는 점이 특징이다.
AI 에이전트의 실행 상태 문제
일반적인 AI 코딩 에이전트는 여러 작업을 순차적으로 실행한다.
예를 들어 다음과 같은 흐름이다.
- 사용자 요청 분석
- 관련 파일 읽기
- 코드 수정 계획 생성
- 파일 변경
- 테스트 실행
- 테스트 결과 분석
- 추가 수정
문제는 이러한 작업이 길어질 수 있다는 점이다.
예를 들어 코드를 수정한 이후 외부 모델 API를 호출하는 도중 실행 환경이 종료될 수 있다.
또는 실제 도구가 작업을 완료했지만 결과를 사용자에게 전달하기 전에 연결이 끊어질 수도 있다.
이때 모든 작업을 처음부터 다시 실행하면 문제가 발생할 수 있다.
특히 파일 수정과 외부 API 호출이 중복 실행될 가능성이 있다.
Cloudflare의 예제는 이러한 문제를 Durable Object의 영속적인 기능과 연결한다.
Tasks
Tasks는 모델 호출과 도구 실행을 기록한다.
예를 들어 파일을 읽는 작업이 완료됐다고 가정해 보자.
이후 실행 환경이 재시작된다.
새로운 실행에서는 이전에 완료한 작업의 결과를 재사용할 수 있다.
따라서 이미 완료된 작업을 불필요하게 반복하지 않을 수 있다.
하지만 중요한 제한사항이 있다.
중단된 작업은 다시 실행될 수 있다.
예를 들어 외부 API 호출이 실제로 성공했지만 완료 기록이 남기 전에 실행 환경이 종료됐다면 같은 API를 다시 호출할 가능성이 있다.
따라서 외부 상태를 변경하는 작업에는 별도의 Idempotency 처리가 필요할 수 있다.
Sessions
Sessions는 대화 기록을 저장한다.
또한 모델에 전달하는 Context를 압축하는 기능을 제공한다.
AI 에이전트는 긴 대화를 유지하면서 많은 파일과 도구 결과를 처리할 수 있다.
하지만 모든 이전 내용을 매번 모델에 전달하면 입력 Token이 증가한다.
따라서 이전 작업의 일부를 요약하거나 압축하는 과정이 필요할 수 있다.
이번 예제에서는 이러한 Session 관리 기능을 활용한다.
하지만 Context를 압축하면 원본 대화의 일부 세부 정보가 생략될 수 있다.
따라서 중요한 실행 결과와 실제 파일의 상태는 별도로 확인해야 할 수 있다.
Streams
Streams는 순서가 있는 이벤트를 저장한다.
예를 들어 다음 이벤트가 발생했다고 가정해 보자.
- 모델이 첫 번째 텍스트 생성
- Tool 호출
- Tool 실행 결과
- 모델이 최종 답변 생성
사용자의 네트워크 연결이 중간에 끊어질 수 있다.
이후 다시 연결하면 저장된 이벤트를 재생할 수 있다.
이러한 구조는 실제 에이전트의 실행 상태와 사용자가 화면에서 확인하는 진행 상황을 분리하는 데 도움이 된다.
Workspace
Workspace는 에이전트가 파일을 읽고 수정할 수 있도록 한다.
이번 예제에서는 지속 저장소와 연결된 Workspace를 사용한다.
따라서 실행 환경이 재시작돼도 일부 작업 파일을 유지할 수 있다.
하지만 실제 파일을 지속 저장소에 저장한다는 사실만으로 모든 파일 변경이 원자적인 트랜잭션으로 처리된다는 의미는 아니다.
실제로 어떤 파일이 변경됐는지와 작업이 완료됐는지 확인하는 로직이 필요할 수 있다.
예제의 실행 구조
공식 예제에서는 다음 구성요소를 연결한다.
LifecycleTasksSessionsStreamsWorkspaceCodexHarness
이러한 기능을 하나의 Durable Object에 설치한다.
모델 요청은 AI Gateway와 연결할 수 있다.
다만 실제 모델을 사용하려면 적절한 인증 정보나 Billing 설정이 필요하다.
이번 예제는 Experimental 상태다.
따라서 실제 운영 환경에서 모든 중단 상황을 복구할 수 있다고 보장하지 않는다.
또한 동일한 날짜에 공개된 Exo와 Nanocode 관련 예제는 별도의 에이전트 Harness 구현으로, 같은 기능의 정식 출시를 의미하지 않는다.
이번 발표는 AI 에이전트의 실행 루프를 서버리스 환경에 배치하면서 작업 기록과 세션, 스트리밍 및 파일 상태를 분리해 관리하는 실험적 아키텍처 사례다.
4️⃣ 개발 도구 및 보안
GitHub Secret Scanning, GitHub Actions Workflow 로그의 Secret 탐지 공개 Preview — 코드 밖에서 노출된 인증 정보까지 검사
- 발표일: 2026-11-09 — 정확한 게시 시각 미확인
- 발표 주체: GitHub
- 안정화 단계: Public Preview
- 적용 대상: GitHub Actions Workflow Logs, GitHub Secret Scanning
- 지원 환경: GitHub Enterprise Cloud
- 필요 라이선스: GitHub Secret Protection 또는 GitHub Advanced Security
- 기본 활성화: 비활성화
- 원문: https://github.blog/changelog/2026-11-09-secret-scanning-detects-secrets-in-github-actions-workflow-logs/
GitHub가 Secret Scanning의 검사 범위를 GitHub Actions Workflow 로그까지 확장했다.
기존 Secret Scanning은 Repository의 소스코드와 여러 GitHub 콘텐츠에서 노출된 인증 정보를 탐지하는 데 사용됐다.
이번 업데이트에서는 CI 실행 과정에서 생성된 로그도 검사한다.
이는 실제 웹 개발 프로젝트의 공급망 보안과 연결되는 변경이다.
소스코드에 없는 Secret도 로그에 노출될 수 있다
예를 들어 Next.js 애플리케이션을 배포하는 GitHub Actions Workflow가 있다고 가정해 보자.
CI에서는 다음 작업을 수행한다.
- 의존성 설치
- 테스트 실행
- 애플리케이션 빌드
- 배포
- 결과 로그 출력
이 과정에서는 외부 서비스에 접근하기 위한 인증 정보가 사용될 수 있다.
대표적으로 다음과 같은 정보다.
- 클라우드 API Key
- Package Registry Token
- 배포용 Credential
- 외부 서비스 Access Token
문제는 이러한 정보가 직접적인 소스코드 변경 없이도 로그에 나타날 수 있다는 점이다.
예를 들어 잘못 작성한 Debug Script가 환경 변수의 값을 출력할 수 있다.
또는 외부 도구가 오류 메시지에 인증 정보를 포함할 수 있다.
이 경우 Secret은 Git Repository의 파일에는 존재하지 않을 수 있다.
따라서 기존의 소스코드 검사만으로는 해당 노출을 발견하기 어려울 수 있다.
새로운 검사 시점
이번 기능은 Workflow Run이 완료된 이후 로그를 검사한다.
예를 들어 CI가 정상적으로 종료됐다고 가정해 보자.
GitHub는 해당 실행의 로그를 검사한다.
알려진 Secret Provider Pattern과 일치하는 정보가 발견되면 Secret Scanning Alert를 생성한다.
Alert에는 문제가 된 GitHub Actions Workflow 로그로 이동할 수 있는 링크가 포함된다.
따라서 관리자는 어떤 작업에서 인증 정보가 노출됐는지 확인할 수 있다.
하지만 Workflow가 실행되는 도중 해당 정보를 즉시 차단하는 기능은 아니다.
검사 시점은 Run이 완료된 이후다.
기존 Secret Scanning과의 통합
새로운 Alert는 기존 Secret Scanning의 Alert 목록에 표시된다.
따라서 별도의 로그 검사 Dashboard를 만들어야 하는 것은 아니다.
또한 지원하는 Secret 유형에는 Validity Check가 실행될 수 있다.
Validity Check는 해당 인증 정보가 실제로 유효한 상태인지 확인하는 기능이다.
하지만 모든 Secret 유형에서 같은 방식의 유효성 검사를 지원하는 것은 아니다.
또한 Validity Check가 실패했다고 해서 해당 인증 정보가 과거에도 안전했다는 의미는 아니다.
실제로 노출됐던 인증 정보라면 교체 여부를 검토해야 할 수 있다.
로그가 삭제된 이후에도 Alert 유지
이번 기능의 중요한 특징은 다음과 같다.
Workflow 로그가 삭제된 이후에도 Secret Scanning Alert는 유지된다.
예를 들어 로그 보관 기간이 만료됐다고 가정해 보자.
원래 로그 파일은 더 이상 존재하지 않을 수 있다.
하지만 이미 발견한 인증 정보 노출 사실은 계속 확인할 수 있다.
이는 보안 담당자가 과거의 노출 문제를 추적하는 데 도움이 된다.
다만 로그가 삭제된 이후에는 원래 출력의 전체 문맥을 다시 확인하기 어려울 수 있다.
따라서 실제 노출이 발견됐다면 관련 실행 기록을 필요한 범위에서 조사해야 한다.
지원 범위
이번 Public Preview는 다음 조건을 충족하는 Enterprise에서 사용할 수 있다.
- GitHub Enterprise Cloud 사용
- GitHub Secret Protection 또는 GitHub Advanced Security 보유
- 해당 Repository에 Secret Scanning 활성화
GHE.com의 Data Residency 환경도 지원한다.
하지만 모든 공개 및 비공개 GitHub Repository에서 기본적으로 사용할 수 있는 기능은 아니다.
실제로 Enterprise 관리자가 정책을 활성화해야 한다.
Enterprise 정책 설정
관리자는 다음 위치에서 기능을 활성화할 수 있다.
Enterprise Settings > Policies > Advanced Security
이후 Actions Workflow 로그의 Secret Scanning 정책을 활성화한다.
기본값은 비활성화다.
따라서 GitHub가 기능을 공개했다는 사실만으로 기존 Enterprise의 모든 Workflow 로그가 자동으로 검사되는 것은 아니다.
과거 로그는 검사하지 않음
이번 Preview에는 중요한 제한사항이 있다.
기능을 활성화한 이후 완료된 Workflow Run만 검사한다.
즉 기존에 저장된 과거 로그 전체를 자동으로 다시 검사하지 않는다.
예를 들어 11월 1일에 인증 정보가 노출된 로그가 존재한다고 가정해 보자.
하지만 관리자가 11월 10일에 기능을 활성화했다.
이 경우 기존 로그가 자동으로 검사 대상에 포함되지는 않는다.
따라서 과거의 인증 정보 노출까지 검토해야 한다면 별도의 방법이 필요할 수 있다.
탐지하지 않는 Secret
이번 Preview에서는 다음 대상을 검사하지 않는다.
- Generic Secret Pattern
- 사용자 정의 Custom Pattern
- AI 기반 Secret 탐지 결과
- GitHub Actions에서 이미 Masking한 Secret
따라서 인증 정보처럼 보이는 모든 문자열을 탐지할 수 있는 것은 아니다.
특히 GitHub Actions의 기본 Masking과 이번 기능은 서로 다른 역할을 수행한다.
Masking은 로그에 특정 인증 정보가 노출되지 않도록 처리하는 기능이다.
이번 Secret Scanning은 실제 로그에 남은 알려진 Secret Pattern을 탐지하는 기능이다.
따라서 Secret Scanning을 활성화했다고 해서 기존 Masking 설정을 제거해서는 안 된다.
Preview 기간의 알림 제한
이번 기능에서는 Alert가 생성되더라도 별도의 알림을 전송하지 않는다.
따라서 관리자나 개발자가 새로운 Secret Alert를 발견하려면 Repository의 Secret Scanning 목록을 직접 확인해야 한다.
이는 현재 Preview의 중요한 제한사항이다.
실제 운영에서는 Alert가 존재한다는 사실과 담당자가 해당 문제를 인지했다는 사실을 구분해야 한다.
적용 범위와 한계
이번 발표에는 실제 Secret 탐지 정확도나 False Positive 비율, 처리 지연 시간에 관한 Benchmark가 포함되지 않았다.
또한 알려지지 않은 Secret 형식까지 모두 탐지하는 기능은 아니다.
이번 업데이트는 소스코드에 저장된 Secret뿐 아니라 CI의 실행 로그에 노출된 인증 정보까지 탐지할 수 있도록 GitHub의 보안 검사 범위를 확장한 Public Preview 기능이다.
Cloudflare Radar Bots Directory, BotBase 기반으로 개편 — AI Bot의 실제 접근 방식과 콘텐츠 사용 목적을 API로 제공
- 발표일: 2026-11-09 — 정확한 게시 시각 미확인
- 발표 주체: Cloudflare
- 안정화 단계: 정식 서비스 및 API 변경
- 적용 대상: Cloudflare Radar, Bots Directory API
- 주요 변경: 복수 Category, Access Level, Content Use 정보
- 호환성: 기존 API 필드 일부 Deprecated
- 원문: https://developers.cloudflare.com/changelog/post/2026-11-09-radar-bots-directory-botbase/
Cloudflare가 Radar의 Bots Directory를 BotBase 기반으로 개편하고 API에서 제공하는 Bot 정보를 확대했다.
이번 업데이트는 새로운 WAF 규칙이나 Bot 차단 정책을 추가하는 기능이 아니다.
실제 Bot과 AI 에이전트를 분류하고 분석하는 데 필요한 Metadata를 확장한 것이다.
특히 AI Bot이 어떤 목적으로 콘텐츠에 접근하는지 구분할 수 있도록 했다는 점이 특징이다.
BotBase
BotBase는 Cloudflare가 추적하는 Bot과 Agent에 관한 카탈로그다.
기존 Bots Directory도 여러 Bot의 정보를 제공했다.
하지만 새롭게 공개된 구조에서는 하나의 Bot이 여러 Category에 속할 수 있도록 변경됐다.
대표적인 필드는 다음과 같다.
categories
기존에는 하나의 Category를 사용하는 구조였다.
하지만 실제 AI 서비스는 여러 목적으로 웹사이트에 접근할 수 있다.
예를 들어 하나의 서비스가 검색 색인을 생성하면서 사용자 질문에 응답하기 위해 웹페이지를 가져올 수도 있다.
따라서 하나의 Category만으로 실제 접근 목적을 표현하기 어려울 수 있다.
새로운 구조에서는 하나의 Bot이 여러 Category에 속할 수 있다.
또한 새로운 COMMERCE Category가 추가됐다.
Access Level
새롭게 제공하는 필드 가운데 하나는 다음과 같다.
accessLevel
지원하는 주요 값은 다음과 같다.
| 값 | 의미 |
|---|---|
DIRECT | Bot이 자신의 신원으로 직접 웹사이트에 접근 |
INTERMEDIARY | 다른 에이전트나 사용자를 대신해 접근 |
예를 들어 일반적인 검색 엔진 크롤러는 자신의 이름으로 웹사이트를 방문할 수 있다.
반면 개인용 AI 비서는 실제 사용자를 대신해 특정 페이지를 가져올 수 있다.
두 요청 모두 자동화된 요청이지만 실제 목적과 신원 관계가 다를 수 있다.
따라서 사이트 운영자는 해당 차이를 고려해 접근 정책을 설계할 수 있다.
하지만 INTERMEDIARY라는 값이 존재한다고 해서 실제 사용자의 신원과 권한을 자동으로 확인할 수 있는 것은 아니다.
사용자가 실제로 어떤 작업을 승인했는지는 별도의 인증 및 권한 체계가 필요하다.
Content Use
두 번째 중요한 필드는 다음과 같다.
contentUse
지원하는 값은 다음과 같다.
| 값 | 의미 |
|---|---|
full | 수집한 콘텐츠를 저장하고 재사용 |
reference | 콘텐츠를 인용하거나 원본 링크를 제공 |
immediate | 사용자의 요청에 한 번 사용 |
예를 들어 AI 모델 학습을 위한 데이터 수집은 full에 해당할 수 있다.
검색 결과에서 원본 링크를 제공하는 경우에는 reference에 해당할 수 있다.
사용자의 질문에 답변하기 위해 특정 페이지를 가져오는 경우에는 immediate가 사용될 수 있다.
이러한 분류는 웹사이트 운영자가 AI Bot의 접근 목적을 이해하는 데 도움이 된다.
다만 이 정보는 Cloudflare가 해당 Bot에 관해 분류한 Metadata다.
실제 요청 하나하나가 반드시 같은 방식으로 콘텐츠를 사용한다는 보장은 아니다.
Bot의 신원 정보
새로운 Directory에서는 다음 정보도 확인할 수 있다.
- Bot Operator
- User-Agent
- Access Level
- Content Use
- Web Bot Auth 검증 정보
Web Bot Auth는 Bot의 신원을 검증하는 데 사용되는 기술과 관련된다.
하지만 알려진 User-Agent를 사용한다는 사실만으로 해당 요청이 실제 Bot에서 발생했다는 보장은 없다.
User-Agent 문자열은 요청을 생성하는 프로그램에서 변경할 수 있기 때문이다.
따라서 신뢰할 수 있는 Bot 식별에는 별도의 검증 방식이 필요할 수 있다.
API의 호환성 변경
이번 업데이트는 실제 API를 사용하는 개발자에게 영향을 줄 수 있다.
대표적인 변경은 다음과 같다.
| 기존 필드 | 새로운 필드 |
|---|---|
category | categories |
operatorUrl | documentationUrl |
기존 category와 operatorUrl은 Deprecated됐다.
또한 다음 필드가 추가됐다.
accessLevelcontentUseoperatorWebsiteUrl
예를 들어 기존 API Client가 다음 구조를 기대한다고 가정해 보자.
category: "AI"
하지만 새로운 API에서는 다음처럼 배열을 사용할 수 있다.
categories: ["AI", "SEARCH"]
이 경우 기존 코드가 단일 문자열을 기대한다면 수정이 필요할 수 있다.
또한 Deprecated된 필드를 계속 사용하는 코드는 향후 API 변경에서 영향을 받을 가능성이 있다.
따라서 실제 API를 사용하는 프로젝트에서는 반환 데이터의 Schema를 확인해야 한다.
적용 범위와 한계
이번 변경은 Bots Directory의 데이터 구조와 UI에 관한 것이다.
모든 AI 에이전트의 실제 목적을 완벽하게 판별하는 기능은 아니다.
또한 Cloudflare가 제공하는 Bot Metadata를 이용해 애플리케이션의 접근 권한을 자동으로 결정하는 것은 신중해야 한다.
실제 사용자 인증과 API 권한 검증은 별도로 필요하다.
이번 발표에는 Bot 탐지 정확도나 API 응답 성능에 관한 정량적인 Benchmark가 포함되지 않았다.
AI Bot의 실제 접근 방식과 콘텐츠 사용 목적을 더 구체적인 Metadata로 제공하고 API를 사용하는 개발자가 해당 정보를 활용할 수 있도록 한 변경이다.
Vercel Container Registry, 압축 이미지 Layer 제한 500MB에서 2GB로 확대
- 발표일: 2026-11-09 — 정확한 게시 시각 미확인
- 발표 주체: Vercel
- 안정화 단계: 정식 플랫폼 업데이트
- 적용 대상: Vercel Container Registry
- Layer 제한: 500MB → 2GB
- 전체 이미지 제한: 기존 15GB 유지
- 원문: https://vercel.com/changelog/vercel-container-registry-now-supports-2-gb-compressed-image-layers
Vercel이 Container Registry에서 하나의 압축 이미지 Layer에 허용하는 크기를 기존 500MB에서 2GB로 확대했다.
Container Image는 여러 Layer로 구성될 수 있다.
예를 들어 다음 요소가 각각 Layer에 포함될 수 있다.
- 운영체제 기반 파일
- 언어 Runtime
- 애플리케이션 의존성
- 실제 애플리케이션 코드
- AI 모델 관련 파일
이러한 구조에서는 전체 이미지 크기뿐 아니라 개별 Layer의 크기도 중요하다.
개별 Layer 제한
기존 Vercel Container Registry에서는 하나의 압축 Layer가 500MB를 초과하면 문제가 발생할 수 있었다.
예를 들어 대규모 Python 라이브러리나 AI 모델 관련 파일을 하나의 Layer에 포함했다고 가정해 보자.
전체 이미지 크기는 15GB보다 작더라도 해당 Layer가 500MB를 초과하면 업로드가 제한될 수 있었다.
새로운 제한은 다음과 같다.
압축 Layer당 최대 2GB
하지만 전체 이미지의 최대 크기는 여전히 15GB다.
따라서 이번 변경을 Container Image 전체 크기 제한이 2GB에서 15GB로 늘어났다는 의미로 해석해서는 안 된다.
실제로 변경된 것은 개별 압축 Layer의 최대 크기다.
Docker CLI와 호환
Vercel Container Registry는 기존 Docker 명령을 지원한다.
대표적인 명령은 다음과 같다.
docker pushdocker pulldocker tag
예를 들어 다음 형태의 이미지 주소를 사용할 수 있다.
vcr.vercel.com/team/project/repository:tag
이미지를 Push하면 필요한 Repository를 자동으로 생성할 수 있다.
따라서 미리 모든 Repository를 생성할 필요가 줄어든다.
Public Repository와 공유 정책
Container Registry에서는 Repository를 공개할 수 있다.
공개 Repository는 다른 Vercel Team에서도 읽기 전용으로 Pull할 수 있다.
또한 특정 Team과 Repository를 공유하는 기능도 제공한다.
지원하는 공유 대상은 최대 100개 Team이다.
따라서 여러 팀이 공통 Base Image를 사용해야 하는 환경에서 활용할 수 있다.
하지만 Public Repository는 다른 팀에서도 이미지를 읽을 수 있다.
따라서 내부 코드나 인증 정보가 포함된 Image를 실수로 공개하지 않도록 주의해야 한다.
GitHub Actions의 OIDC 인증
이번 공식 발표에서는 GitHub Actions와의 연결 방법도 소개한다.
대표적인 Action은 다음과 같다.
vercel/vcr-action/login
이 Action은 GitHub OIDC를 이용한 인증을 지원한다.
따라서 장기간 유효한 Registry Credential을 GitHub Secret에 직접 저장할 필요를 줄일 수 있다.
OIDC 기반 인증은 실제 CI 실행의 신원을 이용해 필요한 시점에 인증할 수 있도록 한다.
하지만 어떤 Repository와 Workflow에 접근 권한을 부여할지는 별도로 관리해야 한다.
CLI와 이미지 변환
Vercel Container Registry는 다음 버전 이상의 CLI에서 관리할 수 있다.
58.3.0
대표적인 명령 그룹은 다음과 같다.
vercel vcr
이미지를 Push한 이후에는 플랫폼 내부에서 추가적인 이미지 최적화 또는 변환이 이루어질 수 있다.
만약 이러한 작업이 실패하면 Dashboard에 오류 상태가 표시된다.
이 경우 같은 이미지를 다시 Push하면 변환을 재시도할 수 있다.
다만 이번 발표에는 Registry 업로드 속도나 실제 이미지 실행 속도가 얼마나 개선되는지에 관한 Benchmark가 포함되지 않았다.
이번 변경은 대규모 의존성과 파일이 포함된 Container Image를 Vercel의 Registry에 더 쉽게 저장할 수 있도록 개별 Layer 크기 제한을 확대한 플랫폼 업데이트다.
Vercel Build, Basic·Standard 빌드 디스크 32GB에서 64GB로 확대 — 모든 요금제에 자동 적용
- 발표일: 2026-11-09 — 정확한 게시 시각 미확인
- 발표 주체: Vercel
- 안정화 단계: 정식 플랫폼 변경
- 적용 대상: Vercel Build
- 변경 전: Basic 및 Standard Build Machine 32GB
- 변경 후: 모든 Build Machine 64GB
- 추가 요금: 없음
- 원문: https://vercel.com/changelog/build-disk-space-on-vercel-increased-to-64-gb
Vercel이 Basic과 Standard Build Machine에서 사용할 수 있는 디스크 공간을 32GB에서 64GB로 확대했다.
Enhanced와 Turbo Build Machine은 이미 64GB를 제공하고 있었다.
따라서 이번 변경 이후에는 모든 Build Machine이 같은 64GB 디스크 공간을 사용할 수 있다.
빌드 디스크 공간이 필요한 이유
일반적인 웹 프로젝트에서도 빌드 과정에서는 많은 파일이 생성될 수 있다.
예를 들어 다음 작업이다.
- npm 또는 pnpm 의존성 설치
- TypeScript 컴파일
- Next.js 및 Astro 빌드
- 정적 페이지 생성
- 이미지 처리
- 테스트 결과 생성
- 코드 생성
실제로 배포되는 결과물은 작더라도 빌드 과정에서 사용하는 임시 파일은 클 수 있다.
예를 들어 Monorepo에서 여러 Package를 설치하고 빌드한다고 가정해 보자.
각 Package에 의존성과 Build Cache가 존재할 수 있다.
이러한 파일이 누적되면 실제 배포 결과물보다 많은 디스크 공간을 사용할 수 있다.
기존 Basic 및 Standard 환경에서는 32GB 제한에 도달할 가능성이 있었다.
이번 변경은 이러한 위험을 줄인다.
모든 Build Machine의 용량 통일
새로운 정책은 다음과 같다.
| Build Machine | 기존 디스크 | 변경 후 |
|---|---|---|
| Basic | 32GB | 64GB |
| Standard | 32GB | 64GB |
| Enhanced | 64GB | 64GB |
| Turbo | 64GB | 64GB |
이번 변경은 모든 요금제에 자동으로 적용된다.
별도의 설정을 변경할 필요는 없다.
또한 추가 요금도 없다.
실제 메모리와는 다른 자원
이번 발표에서 중요한 부분은 디스크 용량과 메모리 용량을 구분해야 한다는 점이다.
예를 들어 빌드 과정에서 Node.js가 메모리를 지나치게 많이 사용한다고 가정해 보자.
이 경우 다음과 같은 오류가 발생할 수 있다.
JavaScript heap out of memory
하지만 디스크 공간이 64GB로 증가했다고 해서 해당 메모리 문제가 자동으로 해결되는 것은 아니다.
CPU와 RAM의 용량은 별도의 자원이다.
또한 이번 변경은 최종 배포 파일의 크기 제한을 모두 확대하는 기능도 아니다.
실제로 증가한 것은 빌드를 수행하는 동안 사용할 수 있는 디스크 공간이다.
성능과 적용 범위
이번 발표에는 빌드 시간이 얼마나 감소하는지에 관한 수치가 포함되지 않았다.
따라서 디스크 공간이 두 배로 늘어났다고 해서 빌드 속도도 두 배가 된다고 판단해서는 안 된다.
실제로 기존 프로젝트가 32GB보다 적은 공간을 사용했다면 이번 변경이 빌드 시간에 직접적인 영향을 주지 않을 수 있다.
반면 기존에 디스크 부족 때문에 빌드가 실패하던 프로젝트에서는 더 큰 의존성과 생성 파일을 처리할 수 있게 됐다.
이번 업데이트는 웹 애플리케이션의 빌드 과정에서 사용할 수 있는 저장 공간을 확대해 디스크 부족으로 인한 빌드 실패 가능성을 줄인 플랫폼 정책 변경이다.
이번 조사 범위에서 이외에 포함할 만한 주요 신규 개발 도구 및 보안 발표는 확인하지 못했습니다.
Node.js, Deno, Bun, Vite, webpack, Rollup, esbuild, Babel, Biome, ESLint, Prettier, npm, pnpm, Yarn 및 GitLab의 공식 릴리스와 변경 기록을 검토했다.
또한 OWASP와 주요 보안 권고 및 공급망 보안 발표를 확인했다.
단순한 개발 브랜치의 Commit이나 새로운 Issue가 존재한다는 사실만으로 정식 패치가 배포됐다고 판단하지 않았다.
이전 브리핑에서 소개한 CodeQL 2.27.3·2.27.4, GitHub Secret Scanning의 기존 탐지기 개선, Dependabot의 Agent Package Manager 지원도 반복하지 않았다.
📚 추천 글
Vue Teleport defer: The Target-Timing Bug Behind a Dozen Patches
- 저자: Parsa Jiravand
- 매체: DEV Community
- 게시일: 2026-11-09
- 기술: Vue 3.5, Teleport, Suspense, SSR, Hydration, DOM Mount Lifecycle
- 원문: https://dev.to/parsajiravand/vue-teleport-defer-the-target-timing-bug-behind-a-dozen-patches-22p9
Vue에서 Modal과 Tooltip을 구현할 때 사용하는 Teleport의 대상 요소가 실제 DOM에 존재하는 시점과 Vue가 해당 요소를 찾는 시점이 달라 발생하는 문제를 자세히 분석한 글이다.
저자는 Vue 3.5.44를 기준으로 실제 동작을 검증했다.
또한 Vue 3.5 계열에서 Teleport와 관련해 수정된 여러 버그의 기록을 조사했다.
이번 글의 특징은 단순히 defer 속성을 추가하라고 설명하지 않는다는 점이다.
실제로 defer가 어떤 상황을 해결하고 어떤 상황에서는 효과가 없는지 구분한다.
Teleport의 기본 구조
Vue의 Teleport는 컴포넌트의 실제 DOM을 다른 위치에 렌더링할 수 있도록 한다.
예를 들어 Modal 컴포넌트가 있다고 가정해 보자.
Modal을 실제 컴포넌트 트리 안에 렌더링하면 부모 요소의 CSS에 영향을 받을 수 있다.
대표적으로 다음 속성이다.
overflow: hiddentransformposition
이러한 스타일 때문에 Modal이 의도하지 않은 위치에 표시되거나 잘릴 수 있다.
Teleport를 사용하면 Modal의 실제 DOM을 다른 요소로 이동할 수 있다.
예를 들어 다음 요소다.
<div id="modal-root"></div>
컴포넌트에서는 다음과 같이 작성할 수 있다.
<Teleport to="#modal-root">
이렇게 하면 실제 Modal의 DOM이 #modal-root 아래에 생성된다.
대상 요소가 아직 존재하지 않는 문제
문제는 Vue가 Teleport의 대상 요소를 언제 찾는지다.
예를 들어 다음 구조를 생각해 보자.
- AppShell
- ModalTrigger
- ModalRoot
ModalTrigger는 먼저 Mount된다.
하지만 ModalRoot는 나중에 Mount된다.
이때 ModalTrigger 안의 Teleport는 다음 요소를 찾으려고 한다.
#modal-root
하지만 실제 DOM에는 아직 해당 요소가 존재하지 않을 수 있다.
그 결과 다음 경고가 발생할 수 있다.
Failed to locate Teleport target
실제 컴포넌트의 데이터는 존재하지만 Modal의 DOM이 표시되지 않을 수 있다.
이러한 문제는 단순히 대상 요소를 코드에 선언했는지 여부와 다르다.
Vue가 실제로 해당 요소를 찾는 순간 DOM에 존재하는지가 중요하다.
동기적인 대상 탐색
저자는 Teleport가 대상 요소를 계속 감시하지 않는다는 점을 강조한다.
일반적인 Teleport는 Mount 과정에서 대상 Selector를 실제 DOM Node로 해석한다.
이때 다음과 비슷한 작업이 발생한다.
document.querySelector("#modal-root")
하지만 대상이 아직 존재하지 않는다면 해당 탐색이 실패할 수 있다.
Vue가 대상이 나타날 때까지 자동으로 계속 재시도하는 것은 아니다.
따라서 실제 DOM의 생성 순서가 중요하다.
Vue 3.5의 defer
Vue 3.5에서는 다음 속성을 사용할 수 있다.
defer
대표적인 형태는 다음과 같다.
<Teleport defer to="#modal-root">
이 속성은 Teleport의 대상 탐색을 현재 Render Cycle의 뒤쪽으로 미룬다.
예를 들어 같은 Render Cycle에서 다음 작업이 발생한다고 가정해 보자.
- ModalTrigger Mount 시작
- Teleport 대상 탐색 보류
- 다른 Sibling Component Mount
#modal-root생성- Teleport가 대상 요소 확인
이 경우 defer를 사용하면 정상적으로 대상 요소를 찾을 수 있다.
즉 같은 Render Cycle 안에서 나중에 생성되는 요소를 사용할 수 있다.
defer가 해결하지 못하는 상황
하지만 defer는 임의의 미래 시점까지 기다리는 기능이 아니다.
예를 들어 ModalRoot가 비동기 API 호출 이후 생성된다고 가정해 보자.
이 경우 첫 번째 Render Cycle이 완료될 때까지도 대상 요소가 존재하지 않을 수 있다.
defer는 해당 대상이 미래의 어떤 시점에 생성될 때까지 무제한 기다리지 않는다.
따라서 다음 상황에서는 여전히 문제가 발생할 수 있다.
- API 응답 이후 대상 생성
- 별도의 비동기 컴포넌트가 나중에 Mount
v-if조건이 나중에 변경- 다른 Suspense Boundary가 완료된 이후 대상 생성
이러한 경우에는 실제 대상이 준비된 이후 Teleport를 Mount하도록 구성할 필요가 있다.
예를 들어 다음 방법을 사용할 수 있다.
- 대상 요소를 애플리케이션의 초기 Layout에 배치
- 대상이 준비됐는지 확인한 뒤 Teleport 렌더링
v-if를 이용해 Mount 시점 제어
Vue의 수정 기록
저자는 Vue 3.5 계열에서 발생한 실제 Teleport 관련 수정 기록을 분석했다.
대표적인 항목은 다음과 같다.
| 버전 | 수정 내용 |
|---|---|
| 3.5.12 | Disabled Teleport와 CSS Variable 업데이트 |
| 3.5.13 | Deferred Teleport의 Mount 이전 업데이트 |
| 3.5.15 | Mount 전후에 도착하는 업데이트 |
| 3.5.21 | Disabled Teleport의 Hydration |
| 3.5.22 | Custom Element 안의 복수 Teleport |
| 3.5.28 | Disabled Teleport의 Anchor 처리 |
| 3.5.31 | Suspense 완료 시점까지 Mount 연기 |
| 3.5.32 | Deferred Mount 완료 전의 업데이트 |
| 3.5.33 | Mount되지 않은 Teleport의 자식 이동 방지 |
| 3.5.39 | SSR에서 중첩된 비동기 Teleport 처리 |
| 3.5.44 | Target Anchor 주변의 자식 Node 순서 수정 |
이러한 수정은 모두 같은 버그를 반복해서 수정한 것이 아니다.
Teleport가 실제로 사용하는 실행 경로가 다양하기 때문에 서로 다른 문제를 처리한 것이다.
SSR과 Hydration
서버 렌더링 환경에서는 문제가 더 복잡해질 수 있다.
예를 들어 서버는 Modal의 HTML을 생성한다.
이후 브라우저에서 Vue가 Hydration을 수행한다.
하지만 실제 클라이언트에서 대상 요소의 생성 순서가 서버와 다를 수 있다.
이 경우 Teleport가 기대한 DOM과 실제 DOM이 일치하지 않을 수 있다.
특히 중첩된 Suspense와 비동기 컴포넌트를 함께 사용하는 경우에는 실행 시점이 복잡해진다.
따라서 Teleport의 대상 요소를 안정적으로 생성하는 것이 중요하다.
여러 Teleport가 같은 대상을 사용하는 경우
하나의 #modal-root를 여러 컴포넌트가 공유할 수 있다.
예를 들어 다음 두 컴포넌트가 있다고 가정해 보자.
- LoginModal
- SettingsModal
두 컴포넌트 모두 같은 Teleport 대상을 사용한다.
이 경우 실제 자식 Node의 순서와 Anchor 위치가 중요할 수 있다.
Vue는 이러한 구조를 처리하기 위해 내부적으로 Anchor Node를 사용한다.
하지만 Mount와 Update의 순서가 복잡해지면 잘못된 위치로 자식 Node를 이동시킬 수 있다.
이번 글에서는 Vue 3.5.44에서 관련 Anchor 순서 문제가 수정됐다는 사실도 확인했다.
적용 범위와 한계
저자는 Vue 3.5.44를 기준으로 테스트했다.
이후 공개된 Vue 3.5.45에는 새로운 Teleport 관련 수정이 포함되지 않았다고 확인했다.
따라서 이번 글은 현재 Vue 3.5 안정 계열의 동작을 설명하는 자료다.
하지만 Vue 3.6의 Vapor Mode는 다른 렌더링 구조를 사용하므로 모든 내부 실행 경로가 동일하다고 가정해서는 안 된다.
이번 글에는 성능 개선에 관한 독립적인 Benchmark는 포함되지 않았다.
Teleport가 실제로 대상 DOM을 찾는 시점을 이해하고, 같은 Render Cycle에서 발생하는 문제와 이후 비동기 실행에서 발생하는 문제를 구분하는 데 필요한 Vue 렌더링 구조를 설명한 글이다.
@starting-style: animate popovers in and out
- 저자: richi.codes
- 매체: 개인 기술 블로그 / DEV Community
- 게시일: 2026-11-06
- 기술: CSS, @starting-style, Popover API, Dialog API, transition-behavior, Web Animations API
- 원문: https://dev.to/richicodes/starting-style-animate-popovers-in-and-out-28hl
CSS의 @starting-style을 이용해 Popover와 Dialog의 진입 애니메이션을 구현하고, 종료 애니메이션이 브라우저마다 다르게 동작하는 이유를 실제 데모로 검증한 글이다.
이번 글의 특징은 CSS 기능의 지원 여부를 하나의 Boolean 값으로 판단하지 않는다는 점이다.
저자는 실제로 Chrome과 Firefox, Safari를 테스트했다.
그리고 다음 두 상황을 구분했다.
- 요소가 나타날 때 실행하는 진입 애니메이션
- 요소가 사라질 때 실행하는 종료 애니메이션
두 작업은 비슷해 보이지만 브라우저의 내부 처리 방식에서는 차이가 존재한다.
기존 CSS Transition의 문제
일반적인 CSS Transition은 두 스타일 상태 사이의 변화를 표현한다.
예를 들어 다음 두 상태가 존재한다고 가정해 보자.
첫 번째 상태는 다음과 같다.
opacity: 0
두 번째 상태는 다음과 같다.
opacity: 1
브라우저는 두 값 사이를 보간해 Fade-in 효과를 만들 수 있다.
하지만 요소가 처음 DOM에 생성되는 순간에는 이전 스타일 상태가 존재하지 않을 수 있다.
예를 들어 다음 작업을 생각해 보자.
appendChild()
새로운 DOM 요소가 추가됐다.
하지만 이전 Frame에서 해당 요소는 존재하지 않았다.
따라서 일반적인 Transition을 적용할 기준 상태가 없을 수 있다.
이러한 상황에서는 요소가 부드럽게 나타나는 대신 즉시 표시될 수 있다.
@starting-style
@starting-style은 새로운 요소가 처음 표시될 때 사용할 시작 스타일을 정의한다.
예를 들어 Popover의 최종 상태가 다음과 같다고 가정해 보자.
- 불투명도 1
- 이동 거리 0
- Scale 1
시작 상태는 다음처럼 설정할 수 있다.
- 불투명도 0
- Y축 아래로 이동
- Scale 0.9
브라우저는 시작 상태에서 최종 상태로 Transition을 실행할 수 있다.
이렇게 하면 새롭게 표시되는 Popover에 Fade-in과 Scale Animation을 적용할 수 있다.
저자는 이러한 진입 애니메이션이 주요 브라우저에서 비교적 일관되게 동작한다고 설명한다.
종료 애니메이션은 다른 문제
Popover를 닫을 때는 다음 문제가 존재한다.
브라우저는 Popover를 닫으면서 해당 요소를 실제 화면의 Top Layer에서 제거할 수 있다.
또한 요소의 display 상태가 변경될 수 있다.
이 경우 opacity를 1에서 0으로 변경하더라도 실제 화면에서는 애니메이션을 완료하기 전에 요소가 사라질 수 있다.
저자는 이를 해결하기 위해 다음 CSS 기능을 사용한다.
transition-behavior: allow-discrete
이 설정은 일반적으로 연속적인 보간이 불가능한 일부 속성을 Transition에 포함할 수 있도록 한다.
대표적인 속성은 다음과 같다.
displayoverlay
하지만 이 두 속성의 브라우저 지원 범위는 같지 않다.
브라우저별 차이
저자가 확인한 결과는 다음과 같다.
| 기능 | Chrome | Firefox | Safari |
|---|---|---|---|
| @starting-style 진입 애니메이션 | 지원 | 지원 | 지원 |
| display Transition | 지원 | 기본 설정에서 제한 | 지원 |
| overlay Transition | 지원 | 제한 | 제한 |
| Popover 종료 애니메이션 | 비교적 안정적 | 제한 존재 | Top Layer 종료 시 제한 |
Firefox에서는 display Transition 관련 구현이 존재하지만 해당 기능이 기본 설정에서 비활성화된 상태였다.
Safari는 display Transition을 지원한다.
하지만 Popover가 Top Layer에서 제거되는 과정을 동일하게 애니메이션 처리하지 못할 수 있다.
따라서 단순히 @starting-style이 지원된다는 사실만으로 종료 애니메이션까지 정상적으로 동작한다고 판단해서는 안 된다.
CSS.supports()의 한계
일반적으로 개발자는 다음 API를 사용할 수 있다.
CSS.supports()
이 함수는 특정 CSS 기능을 브라우저가 지원하는지 확인하는 데 사용된다.
하지만 저자는 이번 실험에서 중요한 문제를 발견했다.
CSS 속성을 지원한다는 결과와 실제 Popover가 해당 속성으로 종료 애니메이션을 실행할 수 있다는 사실은 다르다.
예를 들어 display Transition을 지원하더라도 Top Layer에서 제거되는 요소의 overlay 처리까지 지원하지 않을 수 있다.
따라서 CSS.supports()가 참을 반환했다는 사실만으로 모든 애니메이션이 정상적으로 실행된다고 보장할 수 없다.
저자는 실제 데모에서 각 브라우저의 Transition Event를 확인하도록 했다.
대표적인 이벤트는 다음과 같다.
transitionrun
이 이벤트를 통해 실제로 어떤 속성의 Transition이 실행됐는지 확인할 수 있다.
Fallback으로 Web Animations API 사용
종료 애니메이션이 지원되지 않는 브라우저에서는 다른 접근을 사용할 수 있다.
예를 들어 JavaScript의 다음 API를 사용할 수 있다.
Element.animate()
이 API는 실제 요소에 Animation을 생성한다.
개발자는 해당 Animation이 완료된 이후 Popover를 닫도록 구성할 수 있다.
이렇게 하면 브라우저가 Popover를 즉시 제거하는 동작과 애니메이션의 완료 시점을 분리할 수 있다.
하지만 이 방식에도 추가적인 관리 비용이 존재한다.
예를 들어 다음 상태를 처리해야 한다.
- 사용자가 종료 중 다시 열기 요청
- 여러 종료 이벤트 중복 발생
- Animation 취소
- Focus 복원
- Light Dismiss
- Escape 키로 닫기
따라서 실제 UI가 복잡하다면 단순한 CSS Transition보다 많은 코드가 필요할 수 있다.
Reduced Motion
저자는 사용자의 움직임 감소 설정도 고려해야 한다고 설명한다.
대표적인 Media Query는 다음과 같다.
prefers-reduced-motion
사용자가 운영체제에서 움직임을 줄이도록 설정했다면 과도한 Fade와 Scale Animation을 생략하거나 줄일 수 있다.
이는 단순히 장식 효과의 문제가 아니다.
실제로 움직임에 민감한 사용자에게는 애니메이션이 불편함을 유발할 수 있기 때문이다.
실제 구현의 한계
이번 글은 직접 작성한 브라우저 데모를 기반으로 한다.
하지만 모든 브라우저 버전과 운영체제를 대상으로 한 독립적인 호환성 시험은 아니다.
특히 브라우저의 Experimental Flag와 최신 구현 상태에 따라 결과가 변경될 수 있다.
또한 이번 글에는 Animation의 CPU 비용이나 Frame Time을 비교한 정량적인 Benchmark는 포함되지 않았다.
새로운 CSS 문법의 지원 여부를 확인할 때 실제 사용자 인터랙션과 브라우저의 Top Layer 처리까지 검증해야 한다는 점을 구체적인 애니메이션 사례로 설명한 글이다.
How I Pushed a High-Density Financial WebApp to 98 Mobile PageSpeed Without Cutting Features
- 저자: Yucheng Wang
- 매체: DEV Community / MarketSightAI
- 게시일: 2026-11-06
- 기술: CSS, JavaScript, PageSpeed Insights, CLS, LCP, content-visibility, SSR
- 원문: https://dev.to/yucheng_wang_bbd49a825d14/how-i-pushed-a-high-density-financial-webapp-to-98-mobile-pagespeed-without-cutting-features-1io
복잡한 금융 데이터 분석 웹 애플리케이션에서 기존 기능을 제거하지 않고 모바일 성능과 시각적 안정성을 개선한 과정을 설명한 글이다.
이번 글은 단순한 정적 콘텐츠 페이지의 최적화 사례가 아니다.
저자가 운영하는 애플리케이션에는 다음 기능이 존재한다.
- 실시간 차트
- 여러 시간대의 데이터 표시
- 거래 기록
- 금융 패턴 분석
- 대규모 데이터 테이블
- 언어별 콘텐츠 제공
따라서 단순히 무거운 기능을 제거하거나 데이터를 일부만 표시하는 방식으로 문제를 해결하기 어려웠다.
저자는 특히 두 가지 목표를 유지해야 했다.
첫 번째는 실제 분석 기능을 제거하지 않는 것이다.
두 번째는 검색 엔진이 모든 데이터를 확인할 수 있도록 서버에서 생성한 DOM 구조를 유지하는 것이다.
실제 발견한 성능 문제
초기 모바일 성능 검사에서는 다음 문제가 발견됐다.
- CLS: 0.302
- LCP: 3.6초
- 접근성 점수: 약 80~87점
CLS(Cumulative Layout Shift)는 화면의 요소가 예기치 않게 이동하는 정도를 측정하는 지표다.
예를 들어 사용자가 버튼을 누르려고 했다고 가정해 보자.
하지만 버튼을 누르기 직전에 상단의 다른 요소가 로딩되면서 전체 화면이 아래로 이동한다.
이 경우 사용자는 원래 의도하지 않은 요소를 누를 수 있다.
저자의 애플리케이션에서는 실제로 이러한 문제가 발생할 수 있는 구조가 존재했다.
모바일에서 116px의 레이아웃 이동
저자는 390×844px 화면을 기준으로 원인을 분석했다.
주요 이동 원인은 다음과 같았다.
| 원인 | 이동 거리 |
|---|---|
| 상단 영역 줄바꿈 | 18px |
| 필터 버튼의 여러 줄 배치 | 74px |
| 모바일 Tab의 지연 생성 | 24px |
전체적으로 약 116px의 아래쪽 이동이 발생했다.
특히 필터 버튼이 문제였다.
초기에는 한 줄로 표시됐다.
하지만 실제 데이터를 가져온 이후 버튼 수가 증가했다.
그러면 필터 영역이 여러 줄로 확장됐다.
그 결과 아래에 존재하는 차트와 분석 영역 전체가 이동했다.
필터 영역의 높이 고정
저자는 필터 영역이 여러 줄로 확장되지 않도록 변경했다.
대표적인 방식은 다음과 같다.
- 높이 36px 고정
- 줄바꿈 금지
- 가로 방향 Overflow 허용
- 터치 환경에서 가로 스크롤 지원
예를 들어 다음 CSS 속성을 사용할 수 있다.
overflow-x: auto
white-space: nowrap
이렇게 하면 필터 버튼의 개수가 증가해도 전체 영역의 높이가 바뀌지 않는다.
사용자는 필요한 경우 가로로 스크롤해 다른 필터를 확인할 수 있다.
하지만 가로 스크롤 구조가 항상 적합한 것은 아니다.
필터 수가 많으면 어떤 필터가 존재하는지 발견하기 어려울 수 있다.
따라서 실제 사용자의 탐색 경험도 고려해야 한다.
모바일 Tab의 사전 렌더링
두 번째 문제는 모바일 Tab이었다.
기존 구현에서는 JavaScript가 실행된 이후 Tab Navigation을 생성했다.
문제는 해당 Tab이 페이지 로딩 약 600ms 이후에 나타났다는 점이다.
그 결과 아래의 콘텐츠가 24px 이동했다.
저자는 이를 해결하기 위해 모바일 Tab의 구조를 초기 HTML에 포함했다.
이후 JavaScript는 기존 요소에 Event Listener를 연결한다.
즉 JavaScript가 실행된 이후 DOM 구조를 새롭게 추가하지 않도록 변경한 것이다.
이러한 방식은 서버 렌더링의 이점을 활용하는 사례다.
사용자에게 실제로 필요한 UI의 공간을 처음부터 확보해 Layout Shift를 줄인다.
CLS 개선 결과
저자가 측정한 결과는 다음과 같다.
| 지표 | 변경 전 | 변경 후 |
|---|---|---|
| CLS | 0.302 | 0.046 |
이는 저자가 직접 측정한 결과다.
독립적인 제3자의 반복 Benchmark는 아니다.
하지만 실제로 어떤 요소가 화면을 이동시켰는지 분석하고 관련 구조를 변경했다는 점에서 참고할 만하다.
모든 데이터를 DOM에 유지해야 했던 이유
이번 프로젝트에는 54종류의 금융 자산과 47종류의 분석 패턴이 존재했다.
이러한 데이터를 표와 카드 형태로 표시했다.
저자는 검색 엔진이 실제 데이터를 확인할 수 있어야 한다고 판단했다.
따라서 일반적인 Virtual List를 사용하지 않았다.
Virtual List는 실제 화면에 보이는 일부 데이터만 DOM에 생성하는 방식이다.
이러한 방법은 DOM Node 수를 줄이는 데 도움이 된다.
하지만 아직 Mount되지 않은 데이터는 초기 DOM에 존재하지 않는다.
따라서 브라우저의 페이지 내 검색이나 일부 자동화 도구에서 해당 데이터를 찾기 어려울 수 있다.
저자는 모든 데이터를 DOM에 유지하면서 실제 렌더링 비용을 줄이는 방법을 선택했다.
content-visibility: auto
저자는 다음 CSS 속성을 사용했다.
content-visibility: auto
이 속성은 브라우저가 화면 밖의 콘텐츠에 대해 일부 렌더링 작업을 건너뛸 수 있도록 한다.
예를 들어 긴 테이블이 있다고 가정해 보자.
사용자가 현재 화면에서 첫 번째 20개 행만 보고 있다.
하지만 실제 DOM에는 수백 개의 행이 존재한다.
content-visibility: auto를 사용하면 브라우저가 화면 밖의 행에 대한 일부 Layout 및 렌더링 작업을 지연할 수 있다.
저자는 다음 속성도 함께 사용했다.
contain-intrinsic-size
실제 행의 높이를 계산하기 전에 예상 크기를 제공하기 위한 것이다.
대표적인 값은 다음과 같다.
0 48px
이렇게 하면 화면 밖의 행을 실제로 계산하기 전에도 대략적인 공간을 확보할 수 있다.
하지만 예상 크기와 실제 행의 높이가 크게 다르면 스크롤 과정에서 추가적인 Layout 변화가 발생할 수 있다.
또한 Table Row에 이러한 속성을 적용하는 경우 실제 브라우저의 Table Layout과 상호작용을 확인해야 한다.
실제 렌더링 성능
저자가 공개한 측정 결과는 다음과 같다.
| 지표 | 변경 전 | 변경 후 |
|---|---|---|
| LCP | 3.6초 | 1.7초 |
| Speed Index | 5.0초 | 1.2초 |
| FCP | 3.6초 | 0.9초 |
이번 개선은 단순히 CSS 속성 하나만 변경한 결과가 아니다.
다음 작업이 함께 포함됐다.
- 초기 HTML 구조 조정
- 모바일 필터 Layout 변경
- 불필요한 DOM 변경 감소
- 화면 밖 콘텐츠의 렌더링 지연
- 스타일의 계산 범위 조정
따라서 전체 개선 수치를 content-visibility 하나의 효과로 해석해서는 안 된다.
접근성 개선
저자는 성능 개선 과정에서 접근성 문제도 확인했다.
특히 Dark Mode에서 보조 텍스트의 대비가 부족했다.
기존에는 다음 Tailwind CSS Class를 사용했다.
text-gray-500
하지만 어두운 배경과의 대비가 충분하지 않은 경우가 존재했다.
저자는 이를 다음 값으로 변경했다.
text-gray-400
또한 일부 텍스트에는 Font Weight를 증가시켰다.
이렇게 하면 실제 사용자가 작은 텍스트를 읽기 쉬워질 수 있다.
터치 대상의 크기도 확대했다.
일부 버튼은 24px 정도의 작은 영역을 사용하고 있었다.
저자는 주요 터치 대상을 최소 36px 수준으로 변경했다.
하지만 36px이 모든 접근성 기준에서 충분한 터치 크기라는 의미는 아니다.
실제 요소의 간격과 주변 콘텐츠, 적용하는 접근성 기준도 고려해야 한다.
최종 PageSpeed 결과
저자가 보고한 모바일 성능 점수는 다음과 같다.
- Pattern Matrix: 98점
- Interactive Workbench: 97점
SEO와 Best Practices에서는 100점을 보고했다.
하지만 모든 접근성 항목이 100점에 도달한 것은 아니다.
실제 페이지에 따라 접근성 점수는 별도로 개선이 필요했다.
따라서 높은 성능 점수가 전체 웹 애플리케이션의 접근성이나 정확성을 자동으로 보장하지 않는다.
측정의 한계
이번 글의 성능 수치는 저자가 직접 수행한 PageSpeed Insights 및 로컬 측정 결과다.
독립적인 제3자가 동일한 조건으로 반복 검증한 Benchmark는 아니다.
또한 여러 최적화를 함께 적용했으므로 각각의 변경이 전체 성능에 얼마나 기여했는지 정확하게 분리하기 어렵다.
이번 글은 대규모 데이터를 DOM에 유지해야 하는 요구사항과 모바일 성능을 동시에 만족시키기 위해 초기 Layout 구조와 브라우저의 렌더링 범위를 조정한 실제 사례다.
TanStack Start + tanstack-fetch: 6 data fetching patterns
- 저자: Mohammad Garmabi
- 매체: 개인 기술 블로그 / DEV Community
- 게시일: 2026-11-07
- 기술: React, TanStack Start, TanStack Router, TanStack Query, TypeScript, SSE
- 원문: https://dev.to/mwmdgmb/tanstack-start-tanstack-fetch-6-data-fetching-patterns-1n32
- 저자 원문: https://mohamadgarmabi.github.io/tanstack-fetch/blog/tanstack-start-1-0-tanstack-fetch-6-real-world-patterns
TanStack Start를 사용하는 React 애플리케이션에서 데이터를 어디에서 가져오고, 어떤 계층이 Cache를 소유하며, SSR에서 인증 정보를 어떻게 전달해야 하는지 여섯 가지 패턴으로 설명한 기술 글이다.
저자는 tanstack-fetch의 개발자다.
따라서 이번 글에는 해당 라이브러리의 활용 방법을 소개하는 성격도 존재한다.
하지만 실제 프론트엔드 아키텍처에서 자주 발생하는 데이터 흐름 문제를 구체적으로 구분한다는 점에서 참고할 만하다.
네 가지 도구의 역할
저자는 다음 기술을 구분한다.
| 기술 | 역할 |
|---|---|
| TanStack Start | Full-stack 애플리케이션 및 SSR |
| TanStack Router | Route와 Navigation, Loader |
| TanStack Query | 서버 데이터의 Cache와 갱신 상태 |
| tanstack-fetch | HTTP 요청과 응답, 오류 처리 |
이번 글의 핵심은 이 네 가지 도구가 같은 역할을 중복해서 수행하지 않도록 하는 것이다.
예를 들어 Router Loader와 TanStack Query가 각각 별도의 Cache를 관리한다고 가정해 보자.
그러면 같은 데이터를 두 곳에서 관리해야 한다.
이후 데이터가 변경되면 두 Cache를 모두 갱신해야 할 수 있다.
저자는 이러한 구조를 피하도록 설명한다.
실제 서버 데이터의 Cache는 TanStack Query가 관리하고, HTTP Client는 요청 실행에 집중하도록 한다.
첫 번째 패턴: 단순한 데이터 조회
가장 단순한 경우에는 컴포넌트에서 useQuery()를 사용할 수 있다.
예를 들어 사용자 목록을 가져온다고 가정해 보자.
Query Key는 다음과 같을 수 있다.
["users"]
실제 API 요청은 HTTP Client가 수행한다.
이때 Query Function은 다음 정보를 전달받을 수 있다.
signal
이는 AbortSignal이다.
개발자는 해당 Signal을 실제 HTTP 요청에 전달할 수 있다.
이렇게 하면 더 이상 필요하지 않은 요청을 취소할 수 있다.
하지만 모든 요청을 자동으로 취소해야 한다는 의미는 아니다.
실제 작업의 성격에 따라 취소 가능한 시점과 결과 처리 정책이 달라질 수 있다.
두 번째 패턴: Router Loader와 Query Cache 공유
이번 글에서 중요한 패턴 가운데 하나는 Loader와 Query Cache를 연결하는 것이다.
예를 들어 사용자가 /users 페이지로 이동한다고 가정해 보자.
Router Loader는 해당 페이지에 필요한 사용자 목록을 가져올 수 있다.
그런데 실제 컴포넌트에서도 useQuery()로 같은 목록을 가져온다면 문제가 발생할 수 있다.
두 작업이 서로 다른 Cache를 사용한다면 같은 API를 두 번 호출할 수 있기 때문이다.
저자는 이를 해결하기 위해 공통 Query Option을 정의한다.
대표적인 API는 다음과 같다.
queryOptions()
이후 Router Loader에서는 다음 API를 사용할 수 있다.
ensureQueryData()
Loader는 해당 Query의 데이터가 Cache에 존재하는지 확인한다.
필요하다면 데이터를 가져온다.
실제 컴포넌트는 같은 Query Option을 사용한다.
따라서 Loader와 컴포넌트가 같은 Cache Entry를 공유할 수 있다.
하지만 Cache의 실제 신선도와 Refetch 여부는 staleTime 등의 설정에 영향을 받는다.
따라서 같은 Query Option을 사용했다는 사실만으로 모든 상황에서 네트워크 요청이 정확히 한 번만 발생한다고 보장할 수는 없다.
세 번째 패턴: SSR과 사용자 인증
SSR에서는 새로운 문제가 발생한다.
브라우저가 가진 인증 정보를 서버가 외부 API에 전달해야 할 수 있기 때문이다.
예를 들어 사용자가 로그인했다고 가정해 보자.
브라우저에는 Session Cookie가 존재한다.
서버는 해당 Cookie를 확인하고 사용자별 데이터를 가져와야 한다.
하지만 서버에서 실행한 외부 API 요청에 브라우저의 Cookie가 자동으로 포함되는 것은 아니다.
따라서 실제 요청의 Context에서 필요한 인증 정보를 가져와야 한다.
저자는 이를 위한 HTTP Client의 구성을 소개한다.
대표적인 기능은 다음과 같다.
createSsrForwardInterceptor()
이 Interceptor는 서버에서 받은 인증 정보를 필요한 외부 요청에 전달하도록 구성할 수 있다.
하지만 여기에는 중요한 보안상 제한이 존재한다.
사용자별 HTTP Client와 Query Cache를 여러 SSR 요청 사이에서 전역으로 공유해서는 안 된다.
예를 들어 사용자 A의 Cookie를 가진 API Client가 있다고 가정해 보자.
이후 사용자 B의 요청에서도 같은 Client를 사용하면 인증 정보가 잘못 전달될 가능성이 있다.
따라서 실제 요청별로 필요한 인증 Context를 구성해야 한다.
또한 외부 API에 전달할 필요가 없는 Header는 전달하지 않는 편이 적절하다.
네 번째 패턴: Mutation과 Cache 갱신
사용자가 실제 데이터를 변경하는 경우도 존재한다.
예를 들어 사용자 이름을 변경했다고 가정해 보자.
서버에는 새로운 이름이 저장됐다.
하지만 클라이언트의 Query Cache에는 이전 이름이 남아 있을 수 있다.
따라서 실제 Mutation 이후 관련 Query를 갱신해야 한다.
저자는 두 가지 방식을 소개한다.
첫 번째는 관련 Query를 무효화하는 것이다.
대표적인 API는 다음과 같다.
invalidateQueries()
이렇게 하면 해당 Query를 다시 가져오도록 할 수 있다.
두 번째는 Optimistic Update다.
실제 서버 응답을 기다리지 않고 먼저 클라이언트의 Cache를 변경한다.
예를 들어 사용자가 Todo를 완료 상태로 변경한다고 가정해 보자.
화면에서는 즉시 완료 상태를 표시한다.
이후 서버 요청이 성공하면 해당 상태를 유지한다.
반면 요청이 실패하면 이전 상태로 Rollback할 수 있다.
이러한 방식에서는 실제 이전 Cache 값을 저장해야 한다.
그렇지 않으면 오류 발생 시 원래 상태를 복원하기 어려울 수 있다.
다섯 번째 패턴: HTTP 오류 처리
HTTP 요청에는 여러 종류의 오류가 발생할 수 있다.
예를 들어 다음 상태를 생각할 수 있다.
- 400: 잘못된 요청
- 401: 인증 필요
- 403: 접근 권한 부족
- 404: 리소스 없음
- 500: 서버 오류
이러한 오류를 모두 같은 사용자 메시지로 처리하는 것은 적절하지 않을 수 있다.
특히 401과 403은 다르다.
401은 일반적으로 인증이 필요하거나 인증 정보가 유효하지 않은 상황과 관련된다.
403은 인증 상태와 별개로 해당 리소스에 접근할 권한이 없는 상황과 관련된다.
따라서 403을 받았다는 이유로 무조건 로그인 화면으로 이동시키면 문제가 될 수 있다.
저자는 HTTP Client가 오류를 일관된 형태로 반환하도록 구성한다.
이후 실제 UI에서는 오류의 종류에 따라 다른 화면을 제공할 수 있다.
여섯 번째 패턴: SSE와 실시간 데이터
일부 페이지에서는 초기 데이터 조회와 실시간 변경 이벤트를 함께 사용한다.
예를 들어 실시간 주문 대시보드를 생각해 보자.
처음 페이지를 열면 REST API로 주문 목록을 가져온다.
이후 새로운 주문이 발생하면 SSE(Server-Sent Events)로 이벤트를 전달받는다.
SSE는 서버에서 브라우저로 이벤트를 전달하는 HTTP 기반 기술이다.
대표적인 API는 다음과 같다.
EventSource
저자는 이러한 구조에서 다음 책임을 구분한다.
- REST API: 초기 데이터 Snapshot 제공
- SSE: 실제 변경 이벤트 전달
- TanStack Query: 현재 데이터 Cache 유지
예를 들어 새로운 주문 이벤트가 도착했다고 가정해 보자.
애플리케이션은 두 가지 방식으로 처리할 수 있다.
첫 번째는 실제 Query Cache에 새로운 주문을 직접 추가하는 것이다.
두 번째는 해당 Query를 무효화해 서버에서 다시 가져오는 것이다.
첫 번째 방법은 추가 요청을 줄일 수 있다.
하지만 이벤트가 누락되거나 순서가 바뀌는 경우 Cache가 실제 서버 상태와 달라질 수 있다.
두 번째 방법은 최신 데이터를 다시 가져올 수 있다.
하지만 추가 네트워크 요청이 발생한다.
따라서 실제 데이터의 구조와 이벤트 신뢰성에 따라 선택해야 한다.
SSE 재연결과 중복 이벤트
SSE 연결은 네트워크 문제로 종료될 수 있다.
이후 브라우저가 다시 연결을 시도할 수 있다.
하지만 그 사이에 발생한 이벤트를 놓칠 수 있다.
또는 재연결 과정에서 일부 이벤트가 중복 전달될 수 있다.
따라서 실제 서비스에서는 다음 정보를 고려할 필요가 있다.
- Event ID
- 마지막으로 처리한 이벤트
- 이벤트 순서
- 누락된 이벤트의 재전송
- 중복 이벤트 처리
- 전체 Snapshot 재조회
저자는 단순히 SSE를 연결하는 것보다 실제 Query Cache의 일관성을 유지하는 것이 중요하다고 설명한다.
구현의 한계
이번 글에는 여러 데이터 조회 패턴의 구체적인 TypeScript 예제가 포함돼 있다.
하지만 특정 방식이 다른 HTTP Client보다 몇 배 빠르다는 독립적인 Benchmark는 없다.
또한 저자가 소개하는 tanstack-fetch는 공식 TanStack 프로젝트가 아닌 독립적인 커뮤니티 패키지다.
따라서 실제 프로젝트에 적용할 때는 패키지의 유지보수 상태와 API 지원 범위를 확인해야 한다.
이번 글은 React Full-stack 애플리케이션에서 Router와 HTTP Client, 서버 상태 Cache, SSR 인증 및 실시간 이벤트의 책임을 명확하게 분리하는 아키텍처 설계 방법을 설명한다.
Lambda Durable Functionsでも二重実行は起こる?Checkpointの境界と冪等性を実機検証
- 원제: Lambda Durable Functionsでも二重実行は起こる?Checkpointの境界と冪等性を実機検証
- 저자: Song
- 매체: Zenn
- 게시일: 2026-11-09
- 기술: AWS Lambda Durable Functions, Python, DynamoDB, S3, Idempotency, Transaction
- 원문: https://zenn.dev/takuyousou/articles/lambda-durable-checkpoint-idempotency
AWS Lambda Durable Functions에서 이전에 완료한 작업을 Checkpoint로 복구할 수 있더라도 실제 외부 데이터베이스의 변경 작업은 중복 실행될 수 있다는 점을 직접 검증한 기술 글이다.
이번 글은 일반적인 서버리스 재시도 기능을 소개하는 자료가 아니다.
저자는 실제 AWS 환경을 구성했다.
그리고 데이터베이스의 값을 변경한 직후 의도적으로 오류를 발생시켰다.
이후 Durable Functions가 해당 작업을 다시 실행하는지 확인했다.
특히 Checkpoint가 존재한다는 사실과 실제 비즈니스 작업이 Exactly-once로 처리된다는 사실은 서로 다르다는 점을 구체적인 실행 기록으로 보여준다.
실험 구조
저자는 세 단계의 Workflow를 만들었다.
첫 번째는 AI 에이전트의 결과를 가져오는 작업이다.
invoke_agent
두 번째는 DynamoDB의 실제 업무 데이터를 변경하는 작업이다.
update_business_data
세 번째는 결과를 S3에 저장하는 작업이다.
save_to_s3
이번 실험에서는 두 번째 단계에 집중했다.
실제 DynamoDB 업데이트를 수행한다.
그리고 업데이트가 성공한 직후 오류를 발생시킨다.
이렇게 하면 실제 외부 상태는 변경됐지만 Durable Step 자체는 실패한 상황을 만들 수 있다.
Checkpoint의 역할
Durable Functions는 완료된 Step의 결과를 기록할 수 있다.
예를 들어 첫 번째 Step이 완료됐다고 가정해 보자.
이후 세 번째 Step에서 오류가 발생한다.
Durable Functions는 이미 완료한 첫 번째 Step의 결과를 재사용할 수 있다.
따라서 전체 Workflow를 처음부터 다시 실행할 필요가 줄어든다.
하지만 Step 내부의 실제 외부 작업이 완료됐는지는 다른 문제다.
예를 들어 다음 흐름이다.
- DynamoDB 업데이트 성공
- 업데이트 직후 오류 발생
- Durable Step 실패
- 해당 Step 재시도
- DynamoDB 업데이트 다시 실행
이 경우 DynamoDB의 값이 두 번 변경될 수 있다.
즉 Durable Functions의 Checkpoint는 완료된 Step을 재사용하는 기능이지 Step 안의 모든 외부 Side Effect를 자동으로 Exactly-once로 만드는 기능은 아니다.
실제 테스트 환경
저자는 다음 환경을 사용했다.
| 항목 | 구성 |
|---|---|
| AWS 리전 | 도쿄, ap-northeast-1 |
| 배포 도구 | AWS SAM |
| Lambda | Python 3.14 |
| 데이터 저장 | DynamoDB |
| 결과 저장 | S3 |
| 로그 | CloudWatch Logs |
| 재시도 횟수 | 최대 2회 |
| 재시도 간격 | 2초 |
실제 데이터베이스에는 두 종류의 값을 기록했다.
첫 번째는 Durable Step의 시도 횟수다.
두 번째는 실제 업무 데이터의 업데이트 횟수다.
이렇게 하면 재시도 횟수와 실제 외부 데이터 변경 횟수를 구분할 수 있다.
첫 번째 구현: 일반적인 Update
첫 번째 구현에서는 DynamoDB의 일반적인 Update를 사용했다.
업데이트는 다음 값을 증가시켰다.
write_count
예를 들어 다음과 같은 작업이다.
ADD write_count 1
첫 번째 시도에서 데이터베이스는 정상적으로 업데이트됐다.
이후 개발자가 의도적으로 발생시킨 오류 때문에 Durable Step은 실패했다.
그러자 동일한 Step이 다시 실행됐다.
두 번째 시도에서도 같은 업데이트가 수행됐다.
결과적으로 write_count는 두 번 증가했다.
이는 실제 외부 데이터베이스에서는 두 번의 변경이 발생했음을 의미한다.
두 번째 구현: Idempotency
저자는 동일한 문제가 발생하지 않도록 두 번째 구현을 만들었다.
이번에는 작업을 식별하기 위한 Operation ID를 사용했다.
예를 들어 다음 ID다.
operation-123
동일한 업무 작업이 다시 실행되더라도 같은 Operation ID를 사용한다.
그리고 DynamoDB Transaction을 이용해 다음 작업을 하나의 원자적인 단위로 처리한다.
- 해당 Operation ID가 이미 처리됐는지 확인
- 아직 처리되지 않았다면 작업 완료 Marker 저장
- 실제 업무 데이터 업데이트
이때 Operation ID를 저장하는 작업에 조건을 적용한다.
대표적인 조건은 다음과 같다.
attribute_not_exists(pk)
이 조건은 동일한 Key가 아직 존재하지 않을 때만 새로운 항목을 생성하도록 한다.
DynamoDB Transaction
저자는 다음 API를 사용했다.
TransactWriteItems
Transaction 안에는 다음 두 작업을 포함했다.
- 처리 완료 Marker 생성
- 실제 업무 데이터 변경
두 작업은 하나의 Transaction으로 처리된다.
따라서 Marker만 저장되고 업무 데이터는 변경되지 않는 상황을 줄일 수 있다.
반대로 업무 데이터만 변경되고 Marker는 저장되지 않는 문제도 줄일 수 있다.
예를 들어 첫 번째 시도에서 실제 업데이트가 성공했다고 가정해 보자.
이후 Durable Step이 실패한다.
두 번째 시도에서는 같은 Operation ID를 사용한다.
하지만 처리 완료 Marker가 이미 존재한다.
따라서 조건이 충족되지 않는다.
이 경우 실제 업무 데이터의 업데이트를 반복하지 않는다.
실제 검증 결과
저자는 두 구현을 같은 재시도 조건에서 비교했다.
| 항목 | 일반적인 Update | Idempotent Transaction |
|---|---|---|
| Durable Step 최대 시도 | 2회 | 2회 |
| 첫 번째 DB 업데이트 | 성공 | 성공 |
| 첫 번째 시도 이후 오류 | 발생 | 발생 |
| 재시도 | 수행 | 수행 |
| 실제 업무 데이터 중복 변경 | 발생 | 방지 |
중요한 점은 두 구현 모두 Durable Functions의 동일한 재시도 기능을 사용했다는 사실이다.
실제로 변경한 것은 데이터베이스 업데이트 방식이다.
따라서 중복 실행을 방지한 원인은 Durable Functions의 Checkpoint가 아니라 업무 데이터를 변경하는 작업의 Idempotency 설계였다.
Operation ID의 수명
이러한 방식에서는 Operation ID를 얼마나 오래 유지할지도 중요하다.
예를 들어 실제로는 같은 작업인데 재시도마다 새로운 Operation ID를 생성한다고 가정해 보자.
그러면 기존 Marker와 일치하지 않는다.
따라서 중복 실행을 방지하기 어려울 수 있다.
반대로 서로 다른 두 작업에 같은 Operation ID를 사용하면 실제로 필요한 두 번째 작업이 차단될 수 있다.
따라서 Operation ID는 실제 비즈니스 작업을 안정적으로 식별해야 한다.
또한 처리 완료 Marker의 저장 기간을 고려해야 한다.
Marker가 삭제된 이후에 오래된 이벤트가 다시 도착한다면 동일한 작업이 새롭게 처리될 수 있다.
같은 Operation ID라도 입력이 다를 수 있음
이번 글의 구현을 실제 운영 서비스에 적용할 때는 다른 문제도 고려해야 한다.
같은 Operation ID를 가진 두 요청이 서로 다른 Payload를 전달할 수 있기 때문이다.
예를 들어 첫 번째 요청은 다음 값을 저장하려고 한다.
amount: 100
두 번째 요청은 같은 Operation ID로 다음 값을 전달한다.
amount: 200
이 경우 단순히 Operation ID가 존재한다는 사실만 확인하면 두 번째 요청을 중복 요청으로 취급할 수 있다.
하지만 실제 데이터가 다르므로 애플리케이션의 요구사항에 따라 오류를 반환해야 할 수 있다.
따라서 Operation ID와 함께 요청의 중요한 필드를 검증하는 구조가 필요할 수 있다.
Durable Functions와 Exactly-once
이번 실험은 서버리스 Workflow의 재시도 의미를 구분하는 데 도움이 된다.
다음 세 가지는 서로 다른 개념이다.
첫 번째는 작업을 재시도하는 기능이다.
두 번째는 완료된 Step을 다시 실행하지 않는 기능이다.
세 번째는 실제 외부 데이터 변경이 정확히 한 번만 반영되도록 만드는 기능이다.
Durable Functions의 Checkpoint는 두 번째 문제를 해결하는 데 도움이 된다.
하지만 세 번째 문제에는 외부 시스템의 Transaction과 Idempotency가 필요할 수 있다.
예를 들어 다음 외부 작업도 같은 문제를 가질 수 있다.
- 결제 승인
- 이메일 발송
- 외부 API의 데이터 변경
- 주문 생성
- 재고 차감
따라서 재시도가 존재하는 시스템에서는 실제 작업이 두 번 실행될 수 있다는 가능성을 고려해야 한다.
실험의 한계
이번 글은 실제 AWS 환경에서 수행한 테스트 결과를 제공한다.
하지만 특정 DynamoDB 업데이트와 고정된 재시도 횟수를 대상으로 한다.
모든 종류의 외부 API와 분산 데이터베이스에 대한 Exactly-once 보장을 검증한 것은 아니다.
또한 DynamoDB Transaction을 사용하면 일반적인 단일 Update보다 처리 비용과 제약이 증가할 수 있다.
실제 Transaction의 제한과 동시성 충돌을 고려해야 한다.
이번 글은 서버리스 Workflow에서 재시도와 Checkpoint를 사용하더라도 실제 외부 작업의 중복 변경을 방지하려면 업무 단위의 Idempotency와 원자적인 데이터 변경이 필요하다는 점을 직접 검증한 자료다.