람다를 사용할 때 성능 개선을 위해 주의해야 할 점을 알아봤다.

2022.01.06

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

안녕하세요, 임채정입니다.
이번에 시험 준비를 하면서 람다를 좀 더 좋은 성능으로 사용하기 위한 내용을 공부했습니다.
그래서 이번 블로그에서 그 내용에 대해 설명하고 예시를 통해 정리하려고 합니다.

아젠다

  1. Timeout (제한 시간)
  2. handler 함수 밖의 초기화 코드
  3. /tmp 파일의 사용
  4. 마무리

1. Timeout (제한 시간)

Timeout의 정의

  • 람다 함수의 제한 시간 설정
    • 설정한 시간이 지날 때까지 함수의 실행이 끝나지 않으면 함수는 실패함
    • 함수의 실행이 예상 외로 오래걸리거나 무한로딩되지 않도록 제한 시간이 지나면 실패시킴
  • 발생 원인
    • 원격 API에 연결할 수 없거나 API 호출에 응답하는 데 오래 걸림
    • API 호출 시 소켓 제한 시간 내에 응답을 받지 못함
    • API 호출 시 Lambda 함수의 제한 시간 내에 응답을 받지 못함
    • 네트워크 문제로 인해 재시도 및 중복 API 요청이 발생시
  • 기본값 3초, 최대값은 900초 (15분)

예시

실제로 Timeout이 되면 어떻게 되는지 테스트 해보겠습니다.

람다 함수의 Timeout의 기본값은 3초입니다.
함수의 [구성]탭의 [일반 구성]에서 확인할 수 있습니다.

편집을 통해 변경도 가능하며 최대 시간 15분까지 설정을 할 수 있습니다.

일단 기본값인 3인 상태에서 람다 함수를 변경해서 Timeout 시간이 지나도 실행이 끝나지 않으면 어떻게 되는지 해보겠습니다.
time 라이브러리를 통해 함수 실행을 6초동안 일시정지 합니다.

import json
import time

def lambda_handler(event, context):
    time.sleep(6)
    return "timeout_test"

함수 실행이 실패했습니다.

에러 메시지를 확인해보면
Task timed out after 3.00 seconds
3초의 작업 시간을 초과했다는 경고문이 출력됐습니다.
Timeout 기능이 성공적으로 작용하고 있다는 것을 확인할 수 있습니다.

2. handler 함수 밖의 초기화 코드

세번째는 handler 함수 밖의 초기화 코드를 잘 정의하는 것입니다.
이렇게만 적어두면 무슨 말일지 조금 헷갈릴 수 가 있으니 직접 코드로 예시를 들면서 설명하겠습니다.

먼저 람다에는 이벤트를 처리하는 함수 코드의 메서드인 핸들러(handler)함수가 있습니다. 이 핸들러 함수에 코드를 추가하면 매번 실행을 할 때마다 함수에 있는 코드를 실행하게 됩니다.

이번 예시에서는 DB와 연결하기 위해 DB를 연결하는 초기화 작업을 한다고 가정했습니다. 연결하는데 시간이 3초 걸린다고 가정하면 함수는 실행할 때마다 매번 3초의 초기화 코드를 실행해야 합니다.

import json
import time

def db_connction():
    time.sleep(2)

def lambda_handler(event, context):
    db_connction()
    return "handler_outside_initialize_test"

해당 함수를 3번 실행시키고 로그를 확인해보겠습니다.

Function Logs 1
REPORT RequestId: 7c3f4495-64e6-489d-8c58-4e394aaffaa3	
Duration: 2003.61 ms	Billed Duration: 2004 ms	Memory Size: 128 MB	Max Memory Used: 37 MB	
Init Duration: 108.22 ms

Function Logs 2
REPORT RequestId: 7baaa590-fd5a-4603-9a25-01be0305c9b1	
Duration: 2003.15 ms	Billed Duration: 2004 ms	Memory Size: 128 MB	Max Memory Used: 37 MB

Function Logs 3
REPORT RequestId: d7d51e0d-634f-4705-ae57-9881b59da6ff	
Duration: 2003.30 ms	Billed Duration: 2004 ms	Memory Size: 128 MB	Max Memory Used: 37 MB

세 번의 실행 로그를 잘 살펴보면 Duration의 시간이 매번 약 2초로 함수 실행마다 db_connction()를 실행하고 있다는 걸 확인할 수 있습니다.

이번에는 만약 이 함수를 핸들러 함수 밖에서 실행을 해보겠습니다. 핸들러 함수 밖에서 db_connction()함수를 실행하면 함수를 여러번 실행했을 때 db_connction()함수는 초기화되어 있는 상태에서 핸들러 안에 있는 함수만 실행하게 됩니다.

import json
import time

def db_connction():
    time.sleep(2)
    
db_connction()

def lambda_handler(event, context):
    return "handler_outside_initialize_test"

이번에도 3번의 실행로그를 봐서 위의 함수와 어떤 점이 다른지 비교해보겠습니다.

Function Logs 1
REPORT RequestId: 6436928a-4147-4cc9-8e66-333e0f6b7da5	
Duration: 1.26 ms	Billed Duration: 2 ms	Memory Size: 128 MB	Max Memory Used: 37 MB	
Init Duration: 2113.04 ms

Function Logs 2
REPORT RequestId: abc14f30-d4db-4b6c-a330-359f0136eb84	
Duration: 0.79 ms	Billed Duration: 1 ms	Memory Size: 128 MB	Max Memory Used: 37 MB

Function Logs 3
REPORT RequestId: b2dedb96-5691-47a4-a78c-24d76b704ee7	
Duration: 0.98 ms	Billed Duration: 1 ms	Memory Size: 128 MB	Max Memory Used: 37 MB

위의 함수가 실행 마다 약 2000ms씩 걸렸던 것과 비교하면 매우 빠른시간으로 함수가 실행됩니다. 이렇게 되는 큰 이유는 db_connction()함수를 핸들러 함수 밖에서 실행함으로써 첫 실행에서 초기화를 하기 때문입니다. ( Init Duration: 2113.04 ms ) 그래서 그 다음부터는 db_connction()함수의 실행을 하지 않고 핸들러 함수의 실행만 하기 때문에 1,2ms라는 빠른 시간으로 함수를 실행할 수 있게 됩니다.

3. /tmp 파일의 사용

  • Lambda 실행 환경에서 호출 사이의 데이터에 대한 임시 캐시를 제공하는 일회성 임시 파일 시스템
  • 실행 환경의 수명 동안 보존되며 새 실행 환경이 생성될 때마다 이 영역은 삭제
  • 단일 호출에서 코드에 필요한 데이터를 보존하는 장소가 필요할 경우에는 따로 서비스를 사용할 필요 없이 /tmp 파일을 통해 간단하게 보존 가능
  • /tmp의 파일 작업은 로컬 하드 디스크와 동일하며 빠른 I/O 처리량을 제공
  • 512MB의 고정된 크기

실제로 예시를 통해 /tmp 파일을 사용해보겠습니다.
다음 예시에서 /tmp 파일newData 라는 파일을 생성하고 내용을 추가했습니다. 그리고 그 내용을 로그로 출력하는 함수를 추가했습니다.

import json
import os

# Write data to tmp file
with open("/tmp/newData", "a") as file_data:
    print("Write data to tmp file", file=file_data)
    print("tmp file using test", file=file_data)
    
def lambda_handler(event, context):
    # Print tmp file data
    with open("/tmp/newData", "r") as file_data:
    
        for each_line in file_data:
            print(each_line)
            
    return "tmp_file_test"

함수를 실행하고 로그에 실제로 /tmp 파일의 내용이 출력이 됐는지 확인해보겠습니다.

Test Event Name
performanceTest

Response
"tmp_file_test"

Function Logs
START RequestId: 6c284a33-18a5-408c-bc5f-61aa500af68e Version: $LATEST
Write data to tmp file
tmp file using test
END RequestId: 6c284a33-18a5-408c-bc5f-61aa500af68e
REPORT RequestId: 6c284a33-18a5-408c-bc5f-61aa500af68e	Duration: 1.18 ms	Billed Duration: 2 ms	Memory Size: 128 MB	Max Memory Used: 37 MB	Init Duration: 110.47 ms

Request ID
6c284a33-18a5-408c-bc5f-61aa500af68e

내용을 확인해보면 파일에 추가했던 두 줄의 내용이 출력됩니다. 이걸로 /tmp 파일이 실제로 존재하고 사용할 수 있는 걸 확인할 수 있습니다.

4. 마무리

지금까지 람다를 좀 더 좋은 퍼포먼스로 사용할 수 있는 방법에 대해 정리해 봤습니다.
위에서 살펴봤듯이 간단한 조작으로 람다를 좀더 빠르게 사용할 수 있습니다.