
필자가 운영중인 '개발 한 스푼'에서는 특정 시간에 모든 유저를 대상으로 푸시알림을 보내고 있다. 많은 양은 아니지만 알림처리를 위해 Spring Batch를 사용하고 있다. 하지만 특정 시간대에 몇 번, 몇 분 실행되는 Spring Batch를 위해 컴퓨팅 파워를 추가로 사용하는건 비용 낭비다.
이번 포스트에서는 AWS Lambda를 활용하여 제로 코스트로 Spring Batch를 운용한 경험을 공유한다.
Batch 실행과 문제
배치 작업을 실행시키는 방법으로는
- 배치 어플리케이션이 실행되어 있는 상태로 존재하다가 이벤트를 받았을 때 작업을 실행하거나
- 특정 때에 어플리케이션을 실행시켜 작업을 진행한 뒤 어플리케이션이 종료되는 형태일 수도 있다.
필자의 푸시알림 배치작업은 후자와 같다. 문제는 배치 작업이 실행될때나 그렇지 않을때나 컴퓨팅 파워(EC2 등)를 필요로 한다는 것이다. 배치 어플리케이션이 실행될 환경이 존재해야한다는 뜻이다. 배치 작업이 실행되지 않을 때는 컴퓨팅 파워를 사용하지 않음에도 비용이 발생하는 것이다.
대안 : 추가비용 없이 Batch 실행시키기
적어도 비용을 추가로 지불하지 않고 Batch를 실행시키기 위해 가능한 방법들을 찾아보았다.
기존 서버가 띄워져 있는 컴퓨터
기존 서버가 실행되고 있는 컴퓨터에 Docker로 Batch 어플리케이션을 잠시 실행시킬 수 있다.
가장 쉽게 해결할 수 있는 방법이라 생각하지만 현재 서버가 띄워져 있는 노드는 프리티어(메모리 1GB)이다. 해당 노드 위에 다른 어플리케이션까지 실행시키는 것은 머지않아 메모리 부족으로 이어질 확률이 높았다.
Github Actions에서 실행시키기
Github Actions 워크플로우가 동작하는 컴퓨팅 파워를 사용하는 방식이다. cronjob으로 Github Actions 워크플로우를 실행시킬 수도 있으니 좋은 방안이라고 생각했지만 DB나 여타 AWS Resource들을 사용하기 위해서는 VPC 내부에서 동작해야한다. 때문에 VPC 내부 리소스를 Self Hosted Runner로 설정해야하는데 컴퓨팅 파워를 추가해야하는 문제로 다시 돌아간다.
AWS Lambda 활용하기
Lambda는 사용시에만 활성화되고 사용하지 않을 때는 비활성화된다. 필요할 때만 사용하고 사용한만큼의 비용만 지불하니 비용 효율적인 방식이다.
해결 : Lambda와 Docker를 이용한 통합
AWS Lambda를 활용하면 비용 효율적으로 배치 어플리케이션을 운용할 수 있으므로 효과적일 뿐만 아니라 EventBridge를 통해 cronjob으로 Lambda를 트리거할 수 있다. 현재 필자의 배치 작업을 위한 요구사항이 모두 만족한다.
AWS Lambda는 Docker Container 실행도 지원한다. Spring Batch를 Docker Container로 Lambda 위에 뛰우면 이후 실행 환경을 EC2나 Fargate로 변경되더라도 쉽게 마이그레이션 할 수 있다.

Lambda에 의존하지 않는 Spring을 위해
Lambda는 외부 이벤트를 받아 우리가 작성한 함수를 트리거한다. javascript나 python과 같은 스크립트 언어는 함수 하나만 작성하면 Lambda 환경에서 이벤트를 작성한 함수로 쉽게 전달해준다.
하지만 java와 같이 컴파일 하는 언어에는 컴파일된 빌드 결과물에 동적으로 코드를 주입할 수 없다. 따라서, 우리가 작성하는 코드 내부에 Lambda가 받은 이벤트를 처리하는 로직을 직접 작성해야한다. Docker 컨테이너를 사용한다고해도 마찬가지다.
spring-cloud-function-adapter-aws 라이브러리나 serverless java Container 라이브러리를 통해서 손쉽게 로직을 추가할 수가 있으나 적지만 비즈니스 로직에 환경(Lambda에 의존)과 관련된 로직들이 들어가게 된다.
배치 어프리케이션은 작업이 커질수록 Lambda에서 실행시키지 못할 가능성이 크다. 때문에 비즈니스 코드가 실행 환경에 의존하는 것은 피하는게 좋다. AWS에서도 이를 인지한 것인지 lambda-web-adapter 라는 기술을 만들었다.
해당 기술은 Docker Image에 설치하여 프록시 개념으로 Lambda로 들어오는 Event를 받아 핸들링한 후에 어플리케이션을 호출한다. Docker를 활용하여 Adapter를 어플리케이션 외부에 두는 것이다.
아래처럼 `public.ecr.aws/awsguru/aws-lambda-adapter`를 이미지에 넣어주기만 하면된다. 또 다른 장점은 API Gateway 뿐만 아니라 Event Bridge나 SNS 등 non-http trigger들도 Web 요청처럼 다룰 수 있다.
# 빌드 파일 옮기기
FROM public.ecr.aws/sam/build-java17:1.116.0-20240430173307 as artifact-image
WORKDIR "/task"
ARG JAR_FILE=./build/libs/adevspoon-batch-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} app.jar
# Lambda Web Adatper 추가 및 실행
FROM public.ecr.aws/docker/library/amazoncorretto:17-al2023-headless
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.3-x86_64 /lambda-adapter /opt/extensions/lambda-adapter
ARG PROFILE
ENV PORT=8080
ENV ACTIVE_PROFILE=${PROFILE}
RUN echo "Active Profile: ${ACTIVE_PROFILE}"
WORKDIR /opt
COPY --from=artifact-image /task/app.jar /opt
CMD ["java", "-jar", "-Duser.timezone=Asia/Seoul", "-Dspring.profiles.active=${ACTIVE_PROFILE}", "app.jar", "--server.port=${PORT}"]
Mac 유저라면 위 Dockerfile로 이미지 빌드 시 반드시 `--platform` 옵션을 linux로 만들자. 그렇지 않다면 Lambda에서 컨테이너 내 lambda-adapter를 인식하지 못한다. 다음과 같이 빌드 하면된다.
docker build -t $DOCKER_TAG --platform linux/x86_64 .
EventBridge 핸들링하기
non-http 이벤트들은 '/events' 엔드포인트가 호출된다. 물론 엔드포인트는 직접 커스텀할 수도 있다.(공식문서 참고) Spring Batch 내 코드나 라이브러리에 Lambda와 관련된 것들은 단 하나도 추가하지 않아도 된다. 단지 Web 요청을 다룰 수 있게만 설정해두면 된다.
package com.adevspoon.batch.controller
import com.adevspoon.batch.job.JobExecutionFacade
import org.slf4j.LoggerFactory
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/events")
class EventProcessController(
private val jobExecutionFacade: JobExecutionFacade
) {
private val logger = LoggerFactory.getLogger(this.javaClass)!!
@PostMapping
fun processEvents(@RequestBody req: Map<String, Any>): String {
logger.info("Batch Job 실행 요청")
jobExecutionFacade.executeJob(req.toString())
return "Events processed"
}
}
Docker, SAM을 이용해 Lambda 배포하기
SAM은 AWS에서 Lambda를 쉽게 배포할 수 있도록 지원하는 IaC이다. `template.yaml` 파일에 Lambda 환경에 대한 설정코드를 적고 SAM cli를 통해 실행시키면 배포된다. 물론 먼저, AWS cli 설치와 람다를 배포할 계정에 대한 configure가 등록되어 있어야 한다.
배치 어플리케이션을 위한 Lambda환경은 아래 코드와 같이 설정했다. SAM 문법에 대한 글은 아니므로 문법에 관한 내용은 넘어가고 주요하게 볼 점은
- Timeout : Docker를 다운받아 실행시키는데 생각보다 긴 시간이 걸린다. 적당히 큰 값으로 설정한다. (필자는 3분)
- ImageUri : Docker 이미지의 Uri이다. tag에 따라 값이 바뀌므로 이미지 푸시 이후 외부에서 값을 수정할 것이다.
- Events : cronjob으로 실행시킨다. EventBridge가 자동으로 생성되고 연결된다. 추가로 실행시 body(Input)값도 넘겨준다.
- VpcConfig : 배치 어플리케이션이 내부 DB에 접근하기 때문에 VPC에서 동작하도록 추가했다.
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
AdevspoonBatchFunction:
Type: AWS::Serverless::Function
Properties:
PackageType: Image
MemorySize: 2048
Timeout: 180
ImageUri: ${ImageUri}
Events:
QuestionPublishedEventBridgeRule:
Type: Schedule
Properties:
Schedule: cron(0 23 * * ? *)
Input: '{"eventName": "questionPublished"}'
VpcConfig:
SecurityGroupIds:
- ${SecurityGroupId}
SubnetIds:
- ${SubnetId1}
- ${SubnetId2}
template.yml의 `ImageUri`가 Docker 이미지 저장소를 참조하고 있다. 먼저, Docker 이미지를 푸시하고 SAM을 실행시킨다. 필자는 해당 과정을 script 파일로 만들어놨다. ECR Registry, Repository, 생성한 Tag를 받아 이미지 빌드 후 ECR에 푸시하고 SAM으로 Lambda를 배포한다.
#!/bin/bash
REGISTRY=$1
REPOSITORY=$2
TAG=$3
DOCKER_TAG="$REGISTRY/$REPOSITORY:$TAG"
# 새로운 태그로 Build
docker build -t $DOCKER_TAG --build-arg PROFILE=prod --platform linux/x86_64 .
# ECR 로그인 - 필요하다면(--profile adevspoon)
aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin $REGISTRY
# ECR에 Push
docker push $DOCKER_TAG
# template.yaml의 ImageUri를 변경해준다. (sg, subnet 도 바꿔주기, 여기서는 생략)
# 아래 명령어는 Mac용
# 리눅스 환경이면 : sed -i -e "s/\${ImagUri}/$DOCKER_TAG/g" template.yml
sed -i '' -e "s/\${ImagUri}/$DOCKER_TAG/g" template.yml
# SAM Build & Deploy
sam build --use-container
sam deploy --no-confirm-changeset --no-fail-on-empty-changeset
Lambda에 의존하지 않으면서 비용 효율적으로 Spring Batch 어플리케이션을 사용할 수 있게 되었다.(아래는 배포가 잘 완료된 모습)

마치며
최대한 비용 효율적 운용하면서도 어플리케이션에서 Lambda환경과 관련된 의존성을 없애기 위해 여기저기 확인하다 운이좋게 lambda-web-adapter 를 찾아냈다. Lambda 환경에 의존하지 않을 수 있으며, Lambda에 http 이벤트나 non-http 이벤트를 다루는 모든 서비스들을 웹 요청처럼 통합할 수 있다는 것이 큰 장점인거 같다. 물론 cold start시 굉장히 느리다는 단점도 있다. 상황에 맞춰 잘 활용해보자.
전체 코드는 아래 링크에서 확인할 수 있다.
GitHub - kids-ground/adevspoon-backend: CS 질문 - adevspoon-backend
CS 질문 - adevspoon-backend. Contribute to kids-ground/adevspoon-backend development by creating an account on GitHub.
github.com
'Spring' 카테고리의 다른 글
이벤트 발행 로직 AOP로 분리하여 선언적으로 처리하기 (0) | 2024.04.24 |
---|---|
MySQL 네임드락으로 분산락 구성, AOP로 유연하게 적용하기 (0) | 2024.04.24 |
예외 정보를 인터페이스와 infix함수로 확장성, 가독성 높게 관리하기 (0) | 2024.04.18 |
API별 인증 해제, 어노테이션으로 효율적으로 처리하기 (2) | 2024.04.09 |
문자열 응답 시 공통 응답형식이 적용되지 않는 문제 해결하기 (0) | 2024.04.05 |

필자가 운영중인 '개발 한 스푼'에서는 특정 시간에 모든 유저를 대상으로 푸시알림을 보내고 있다. 많은 양은 아니지만 알림처리를 위해 Spring Batch를 사용하고 있다. 하지만 특정 시간대에 몇 번, 몇 분 실행되는 Spring Batch를 위해 컴퓨팅 파워를 추가로 사용하는건 비용 낭비다.
이번 포스트에서는 AWS Lambda를 활용하여 제로 코스트로 Spring Batch를 운용한 경험을 공유한다.
Batch 실행과 문제
배치 작업을 실행시키는 방법으로는
- 배치 어플리케이션이 실행되어 있는 상태로 존재하다가 이벤트를 받았을 때 작업을 실행하거나
- 특정 때에 어플리케이션을 실행시켜 작업을 진행한 뒤 어플리케이션이 종료되는 형태일 수도 있다.
필자의 푸시알림 배치작업은 후자와 같다. 문제는 배치 작업이 실행될때나 그렇지 않을때나 컴퓨팅 파워(EC2 등)를 필요로 한다는 것이다. 배치 어플리케이션이 실행될 환경이 존재해야한다는 뜻이다. 배치 작업이 실행되지 않을 때는 컴퓨팅 파워를 사용하지 않음에도 비용이 발생하는 것이다.
대안 : 추가비용 없이 Batch 실행시키기
적어도 비용을 추가로 지불하지 않고 Batch를 실행시키기 위해 가능한 방법들을 찾아보았다.
기존 서버가 띄워져 있는 컴퓨터
기존 서버가 실행되고 있는 컴퓨터에 Docker로 Batch 어플리케이션을 잠시 실행시킬 수 있다.
가장 쉽게 해결할 수 있는 방법이라 생각하지만 현재 서버가 띄워져 있는 노드는 프리티어(메모리 1GB)이다. 해당 노드 위에 다른 어플리케이션까지 실행시키는 것은 머지않아 메모리 부족으로 이어질 확률이 높았다.
Github Actions에서 실행시키기
Github Actions 워크플로우가 동작하는 컴퓨팅 파워를 사용하는 방식이다. cronjob으로 Github Actions 워크플로우를 실행시킬 수도 있으니 좋은 방안이라고 생각했지만 DB나 여타 AWS Resource들을 사용하기 위해서는 VPC 내부에서 동작해야한다. 때문에 VPC 내부 리소스를 Self Hosted Runner로 설정해야하는데 컴퓨팅 파워를 추가해야하는 문제로 다시 돌아간다.
AWS Lambda 활용하기
Lambda는 사용시에만 활성화되고 사용하지 않을 때는 비활성화된다. 필요할 때만 사용하고 사용한만큼의 비용만 지불하니 비용 효율적인 방식이다.
해결 : Lambda와 Docker를 이용한 통합
AWS Lambda를 활용하면 비용 효율적으로 배치 어플리케이션을 운용할 수 있으므로 효과적일 뿐만 아니라 EventBridge를 통해 cronjob으로 Lambda를 트리거할 수 있다. 현재 필자의 배치 작업을 위한 요구사항이 모두 만족한다.
AWS Lambda는 Docker Container 실행도 지원한다. Spring Batch를 Docker Container로 Lambda 위에 뛰우면 이후 실행 환경을 EC2나 Fargate로 변경되더라도 쉽게 마이그레이션 할 수 있다.

Lambda에 의존하지 않는 Spring을 위해
Lambda는 외부 이벤트를 받아 우리가 작성한 함수를 트리거한다. javascript나 python과 같은 스크립트 언어는 함수 하나만 작성하면 Lambda 환경에서 이벤트를 작성한 함수로 쉽게 전달해준다.
하지만 java와 같이 컴파일 하는 언어에는 컴파일된 빌드 결과물에 동적으로 코드를 주입할 수 없다. 따라서, 우리가 작성하는 코드 내부에 Lambda가 받은 이벤트를 처리하는 로직을 직접 작성해야한다. Docker 컨테이너를 사용한다고해도 마찬가지다.
spring-cloud-function-adapter-aws 라이브러리나 serverless java Container 라이브러리를 통해서 손쉽게 로직을 추가할 수가 있으나 적지만 비즈니스 로직에 환경(Lambda에 의존)과 관련된 로직들이 들어가게 된다.
배치 어프리케이션은 작업이 커질수록 Lambda에서 실행시키지 못할 가능성이 크다. 때문에 비즈니스 코드가 실행 환경에 의존하는 것은 피하는게 좋다. AWS에서도 이를 인지한 것인지 lambda-web-adapter 라는 기술을 만들었다.
해당 기술은 Docker Image에 설치하여 프록시 개념으로 Lambda로 들어오는 Event를 받아 핸들링한 후에 어플리케이션을 호출한다. Docker를 활용하여 Adapter를 어플리케이션 외부에 두는 것이다.
아래처럼 public.ecr.aws/awsguru/aws-lambda-adapter
를 이미지에 넣어주기만 하면된다. 또 다른 장점은 API Gateway 뿐만 아니라 Event Bridge나 SNS 등 non-http trigger들도 Web 요청처럼 다룰 수 있다.
# 빌드 파일 옮기기
FROM public.ecr.aws/sam/build-java17:1.116.0-20240430173307 as artifact-image
WORKDIR "/task"
ARG JAR_FILE=./build/libs/adevspoon-batch-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} app.jar
# Lambda Web Adatper 추가 및 실행
FROM public.ecr.aws/docker/library/amazoncorretto:17-al2023-headless
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.3-x86_64 /lambda-adapter /opt/extensions/lambda-adapter
ARG PROFILE
ENV PORT=8080
ENV ACTIVE_PROFILE=${PROFILE}
RUN echo "Active Profile: ${ACTIVE_PROFILE}"
WORKDIR /opt
COPY --from=artifact-image /task/app.jar /opt
CMD ["java", "-jar", "-Duser.timezone=Asia/Seoul", "-Dspring.profiles.active=${ACTIVE_PROFILE}", "app.jar", "--server.port=${PORT}"]
Mac 유저라면 위 Dockerfile로 이미지 빌드 시 반드시 --platform
옵션을 linux로 만들자. 그렇지 않다면 Lambda에서 컨테이너 내 lambda-adapter를 인식하지 못한다. 다음과 같이 빌드 하면된다.
docker build -t $DOCKER_TAG --platform linux/x86_64 .
EventBridge 핸들링하기
non-http 이벤트들은 '/events' 엔드포인트가 호출된다. 물론 엔드포인트는 직접 커스텀할 수도 있다.(공식문서 참고) Spring Batch 내 코드나 라이브러리에 Lambda와 관련된 것들은 단 하나도 추가하지 않아도 된다. 단지 Web 요청을 다룰 수 있게만 설정해두면 된다.
package com.adevspoon.batch.controller
import com.adevspoon.batch.job.JobExecutionFacade
import org.slf4j.LoggerFactory
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/events")
class EventProcessController(
private val jobExecutionFacade: JobExecutionFacade
) {
private val logger = LoggerFactory.getLogger(this.javaClass)!!
@PostMapping
fun processEvents(@RequestBody req: Map<String, Any>): String {
logger.info("Batch Job 실행 요청")
jobExecutionFacade.executeJob(req.toString())
return "Events processed"
}
}
Docker, SAM을 이용해 Lambda 배포하기
SAM은 AWS에서 Lambda를 쉽게 배포할 수 있도록 지원하는 IaC이다. template.yaml
파일에 Lambda 환경에 대한 설정코드를 적고 SAM cli를 통해 실행시키면 배포된다. 물론 먼저, AWS cli 설치와 람다를 배포할 계정에 대한 configure가 등록되어 있어야 한다.
배치 어플리케이션을 위한 Lambda환경은 아래 코드와 같이 설정했다. SAM 문법에 대한 글은 아니므로 문법에 관한 내용은 넘어가고 주요하게 볼 점은
- Timeout : Docker를 다운받아 실행시키는데 생각보다 긴 시간이 걸린다. 적당히 큰 값으로 설정한다. (필자는 3분)
- ImageUri : Docker 이미지의 Uri이다. tag에 따라 값이 바뀌므로 이미지 푸시 이후 외부에서 값을 수정할 것이다.
- Events : cronjob으로 실행시킨다. EventBridge가 자동으로 생성되고 연결된다. 추가로 실행시 body(Input)값도 넘겨준다.
- VpcConfig : 배치 어플리케이션이 내부 DB에 접근하기 때문에 VPC에서 동작하도록 추가했다.
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
AdevspoonBatchFunction:
Type: AWS::Serverless::Function
Properties:
PackageType: Image
MemorySize: 2048
Timeout: 180
ImageUri: ${ImageUri}
Events:
QuestionPublishedEventBridgeRule:
Type: Schedule
Properties:
Schedule: cron(0 23 * * ? *)
Input: '{"eventName": "questionPublished"}'
VpcConfig:
SecurityGroupIds:
- ${SecurityGroupId}
SubnetIds:
- ${SubnetId1}
- ${SubnetId2}
template.yml의 ImageUri
가 Docker 이미지 저장소를 참조하고 있다. 먼저, Docker 이미지를 푸시하고 SAM을 실행시킨다. 필자는 해당 과정을 script 파일로 만들어놨다. ECR Registry, Repository, 생성한 Tag를 받아 이미지 빌드 후 ECR에 푸시하고 SAM으로 Lambda를 배포한다.
#!/bin/bash
REGISTRY=$1
REPOSITORY=$2
TAG=$3
DOCKER_TAG="$REGISTRY/$REPOSITORY:$TAG"
# 새로운 태그로 Build
docker build -t $DOCKER_TAG --build-arg PROFILE=prod --platform linux/x86_64 .
# ECR 로그인 - 필요하다면(--profile adevspoon)
aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin $REGISTRY
# ECR에 Push
docker push $DOCKER_TAG
# template.yaml의 ImageUri를 변경해준다. (sg, subnet 도 바꿔주기, 여기서는 생략)
# 아래 명령어는 Mac용
# 리눅스 환경이면 : sed -i -e "s/\${ImagUri}/$DOCKER_TAG/g" template.yml
sed -i '' -e "s/\${ImagUri}/$DOCKER_TAG/g" template.yml
# SAM Build & Deploy
sam build --use-container
sam deploy --no-confirm-changeset --no-fail-on-empty-changeset
Lambda에 의존하지 않으면서 비용 효율적으로 Spring Batch 어플리케이션을 사용할 수 있게 되었다.(아래는 배포가 잘 완료된 모습)

마치며
최대한 비용 효율적 운용하면서도 어플리케이션에서 Lambda환경과 관련된 의존성을 없애기 위해 여기저기 확인하다 운이좋게 lambda-web-adapter 를 찾아냈다. Lambda 환경에 의존하지 않을 수 있으며, Lambda에 http 이벤트나 non-http 이벤트를 다루는 모든 서비스들을 웹 요청처럼 통합할 수 있다는 것이 큰 장점인거 같다. 물론 cold start시 굉장히 느리다는 단점도 있다. 상황에 맞춰 잘 활용해보자.
전체 코드는 아래 링크에서 확인할 수 있다.
GitHub - kids-ground/adevspoon-backend: CS 질문 - adevspoon-backend
CS 질문 - adevspoon-backend. Contribute to kids-ground/adevspoon-backend development by creating an account on GitHub.
github.com
'Spring' 카테고리의 다른 글
이벤트 발행 로직 AOP로 분리하여 선언적으로 처리하기 (0) | 2024.04.24 |
---|---|
MySQL 네임드락으로 분산락 구성, AOP로 유연하게 적용하기 (0) | 2024.04.24 |
예외 정보를 인터페이스와 infix함수로 확장성, 가독성 높게 관리하기 (0) | 2024.04.18 |
API별 인증 해제, 어노테이션으로 효율적으로 처리하기 (2) | 2024.04.09 |
문자열 응답 시 공통 응답형식이 적용되지 않는 문제 해결하기 (0) | 2024.04.05 |