서버리스를 재고하다: FLAME 기술
(fly.io)- FLAME은 기존 애플리케이션 코드 일부를 함수로 감싸 임시 애플리케이션 복사본에서 실행하게 해, 별도 런타임으로 코드를 옮기지 않고 세밀한 탄력 확장을 노림
- 기존 FaaS 방식은 비디오 썸네일 생성 같은 작업에서도 HTTP, S3, SQS, API Gateway, 인코더/디코더와 워크플로 오케스트레이션이 늘어나는 구조를 만들 수 있음
- Elixir의 flame library는
FLAME.call로 함수 실행을 원격 러너에 위임하고, Fly.io의FLAME.FlyBackend는 같은 Docker 이미지로 새 Fly Machine을 부팅해 약 3초 안에 부모 노드로 연결함 - 개발·테스트에서는
LocalBackend로 같은 런타임에서 실행하고, 프로덕션에서는min,max,max_concurrency,idle_shutdown_after로 scale-to-zero와 짧은 hot 상태 유지를 조정함 - FLAME은 작업 큐를 없애기보다 내구성 보장과 탄력 실행을 분리해, 큐는 dispatch·commit·retry를 맡고 CPU 집약 작업은 FLAME 호출 안에서 처리하게 함
FLAME 패턴이 겨냥하는 문제
- 탄력적 자동 확장은 서버 관리 부담을 줄이고 사용량 기반 비용을 약속하지만, FaaS를 쓰면 별도 큐, 저장소, 글루 코드, 개발·테스트·CI 복잡도가 함께 늘어날 수 있음
- FLAME은 Fleeting Lambda Application for Modular Execution의 약자로, 애플리케이션 전체를 lambda처럼 다루고 일부 모듈 작업만 짧게 뜨는 인프라에서 실행하는 방식임
- 목표는 세 가지로 정리됨
fly deploy,git push heroku,kubectl같은 기존 배포 흐름으로 서버 관리를 줄임- 애플리케이션 코드의 특정 부분만 온디맨드 세밀 확장함
- 애플리케이션을 다시 쓰거나 독점 런타임으로 일부 코드를 옮기지 않음
FLAME.call로 기존 함수 옮기기
- 예시는 Elixir 애플리케이션에서 업로드된 비디오를
ffmpeg로 썸네일로 변환하는generate_thumbnails함수임 - 기존 함수는 임시 디렉터리를 만들고
ffmpeg를 실행한 뒤, 생성된 썸네일을 내구성 저장소에 저장하고 URL을 DB에Repo.insert_all로 기록함 - CPU 집약적인 비디오 트랜스코딩은 프로덕션에서 서비스 전체를 멈추게 할 수 있으므로, 코드 전체를 FaaS나 마이크로서비스로 옮기지 않고 함수 본문을
FLAME.call(MyApp.FFMpegRunner, fn -> ... end)로 감쌈 FLAME.call은 러너 풀 이름과 함수를 받아 애플리케이션 전체의 새 복사본을 찾거나 부팅한 뒤 그 함수만 실행함- 함수가 닫아 잡은
%Video{}구조체와interval같은 변수는 자동으로 전달됨 - FLAME 러너는 부팅 후 부모 노드에 연결하고, 실행할 함수를 받은 뒤 결과를 호출자에게 반환함
- 설정에 따라 러너는 추가 작업을 기다리다가 idle 상태에서 내려가거나 즉시 종료됨
- 함수가 닫아 잡은
- DB 연결을 포함한 애플리케이션 전체가 실행되므로, 원래 코드처럼
Repo.insert_all을 그대로 사용할 수 있음
FaaS 복잡성과 FLAME의 차이
- FaaS는 문제를 풀기 위한 구성요소를 제공하지만, FLAME은 별도 통신 계층 자체를 줄이는 접근에 가까움
- 비디오 썸네일 예시에서 단순한 AWS Lambda Function URL로 시작해도 곧 복잡도가 생김
- HTTP로 썸네일을 앱에 다시 스트리밍하려면 양쪽에 커스텀 인코더와 디코더를 작성해야 함
- 비디오 트랜스코딩이나 업로드가 15분을 넘으면 Lambda의 하드 타임아웃 때문에 영상을 청크로 나누고 더 많은 Lambda를 써야 함
- 워크플로 오케스트레이션과 SQS, S3 같은 추가 서비스가 필요해짐
- FaaS 기반 구현은 보통 다음 비용 지점을 만듦
- HTTP endpoint, S3, API Gateway로 Lambda 트리거
- 비디오 트랜스코딩용 맞춤 Lambda 작성
- 썸네일 결과를 SQS에 저장
- 앱 쪽 SQS consumer 작성
- DB에 저장하고, 다른 인스턴스에 연결된 활성 구독자에게 이벤트를 돌려보낼 방법 구성
- FLAME은 애플리케이션 내부 코드와 기존 DB, PubSub, 플랫폼 기능을 그대로 쓰게 하므로 별도 서비스와 결과 회수용 저장소를 줄일 수 있음
Fly.io 백엔드와 로컬 실행
- Elixir flame library는 FLAME 패턴 구현체이며, 기본으로
LocalBackend와FlyBackend를 제공함 FLAME.FlyBackend는 Fly.io 인프라에서 새 Machine에 애플리케이션 복사본을 부팅하고 약 3초 안에 부모 노드로 연결해 작업을 받을 수 있음- Fly.io는 애플리케이션을 패키징된 Docker 이미지로 실행하므로, Fly API에 현재 앱과 같은 이미지를 가진 새 Machine 부팅을 요청함
- Fly.io 인프라에서는 FLAME 러너를 부모와 같은 리전에 시작할 수 있어 부모와 러너 사이의 지연시간을 줄일 수 있음
FLAME.FlyBackend는 문서 포함 200 LOC 미만이며, 라이브러리 의존성은 HTTP 클라이언트req하나임- 개발과 테스트에서는 LocalBackend로 노트북이나 CI 서버의 기존 런타임에서 해당 코드를 실행함
파일 전달과 개발·테스트 단순화
- FLAME은 애플리케이션 밖에 코드를 쓰지 않고도 비즈니스 로직, DB 설정, PubSub, 플랫폼 기능을 재사용할 수 있음
- Elixir에서는 Erlang VM의 분산 기능 덕분에 부모 노드의 파일 스트림을 원격 FLAME 애플리케이션으로 보낼 수 있음
- 부모 노드에서 비디오 경로의 파일 스트림을 엶
- FLAME 자식에서 임시 파일 스트림을 열고 부모 스트림을 복사함
- 이후
ffmpeg는 원격 러너의 임시 파일을 입력으로 썸네일을 생성함
- 이 방식에서는 S3나 HTTP 인터페이스를 별도로 설정하지 않아도 파일을 FLAME 서버로 전달할 수 있음
- 별도 서비스 배포, endpoint 관리, S3/SQS를 통한 결과 회수, 개발·테스트·CI 의존성 구성이 줄어듦
Elixir 밖에서의 FLAME
- Elixir는 프로세스 감독과 분산 메시징을 제공해 FLAME 모델에 잘 맞지만, 합리적인 동시성 프리미티브를 가진 언어도 이 패턴을 쓸 수 있음
- JavaScript 개념 증명 예시는 Fly Machine에서 애플리케이션 함수를 다른 머신으로 실행하는 형태임: fly-run-this-function-on-another-machine
- JavaScript 기반 FLAME 호출의 일반 흐름은 모듈 실행 부분을 새 파일로 옮기고 러너 풀에서 실행하는 방식임
- 인자가 JSON 직렬화 가능하면 전체 흐름은 Elixir 예시와 유사하게, 애플리케이션 코드가 짧게 뜨는 인스턴스에서 실행됨
- 완전한 FLAME 라이브러리는 다음을 처리해야 함
- 탄력적 풀 scale-up과 scale-down 로직
- hot startup과 cold startup을 고려한 풀 관리
- orphaned resource 방지를 위한 원격 러너 모니터링
- 배포를 최신 상태로 유지하는 방식
백그라운드 작업 큐와의 관계
- FLAME은 백그라운드 작업 처리기 안에서도 동작하지만, 작업 큐와 역할이 겹치는 부분이 있음
- 작업 큐는 보통 내구성 보장이 필요할 때 사용하며, 큐가 부하 변화에 맞춰 더 많은 작업을 처리하도록 조정할 수 있음
- 내구성 있는 작업과 탄력 실행은 별개의 관심사임
- 큐를 단순 offload 실행 용도로만 쓰면 작업에 데이터를 넣고, 결과를 호출자나 사용자 디바이스로 되돌리는 글루 코드가 필요해짐
- 비디오 업로드 후 썸네일 생성 성공을 보장해야 한다면 큐가 dispatch, commit, retry 메커니즘을 맡을 수 있음
- 실제 트랜스코딩은 작업 내부의 FLAME 호출로 실행해 내구성과 확장 실행을 분리할 수 있음
- 저장 전 동영상 미리보기나 사용자가 이미 앱을 떠난 ML 모델 실행처럼 내구성이 필요 없는 작업은 내구성 저장소에 작업을 쓰는 구조와 맞지 않을 수 있음
탄력 확장을 위한 러너 풀
- Elixir FLAME 구현은 러너의 탄력 풀을 정의해 scale-to-zero와 동시성 제한을 함께 지원함
- 예시 설정은
FLAME.Pool을 애플리케이션의start/2에 추가함min: 0으로 scale-to-zero 허용max: 10으로 최대 10개 러너 부팅max_concurrency: 5로 러너당ffmpeg작업 5개 지원idle_shutdown_after: 30_000으로 호출 작업이 30초 없으면 idle down
- FLAME 부모 존재 여부에 따라 Phoenix 웹서버는 조건부로 시작함
- 웹 트래픽을 처리하지 않는 FLAME 러너에서는 웹서버를 시작할 필요가 없음
- DB 같은
MyApp.Repo는 FLAME 러너 내부에서 사용해야 하므로 그대로 둠
min: 1을 주면 애플리케이션 시작 시점부터 최소 하나의ffmpeg러너를 hot 상태로 유지할 수 있음
상태 있는 프로세스 배치
- Elixir 애플리케이션의 상태 있는 부분은 메시지 mailbox를 가진 경량 프로세스 프리미티브를 중심으로 구성됨
FLAME.call과FLAME.cast는 비교적 stateless한 코드에 적합하고,FLAME.place_child는 기존 프로세스 명세를 로컬이 아닌 FLAME 러너에서 시작하게 함FLAME.place_child는Task.Supervisor.start_child,DynamicSupervisor.start_child같은 인터페이스를 쓰는 위치에서 사용할 수 있음- LiveView 업로드 중 썸네일 생성 예시에서는 업로드 chunk를
ThumbnailGenerator프로세스에 전달하고, 해당 프로세스가ffmpeg와 통신함ffmpegstdout에서 PNG delimiter를 찾으면 LiveView 프로세스에 이미지 메시지를 보냄- LiveView는
handle_info에서 메시지를 받아 UI에 새 이미지를 추가함
- 기존
DynamicSupervisor.start_child(@sup, spec)호출을FLAME.place_child(Thumbs.FFMpegRunner, spec)로 바꾸면ThumbnailGenerator프로세스가 FLAME 러너에서 실행됨 - 프로세스는 위치와 상관없이 메시지를 주고받을 수 있으므로, 업로드 종료나 브라우저 탭 종료로 프로세스가 끝나면 FLAME 서버는 종료를 감지하고 다른 작업이 없을 때 idle down함
원격 모니터링과 실패 처리
- 짧게 뜨는 인프라에는 orphaned resource를 막기 위한 failsafe가 필요함
- 부모가 러너를 띄우면 러너는 작업이 없을 때 스스로 idle down해야 하고, 부모 노드와 더 이상 연락할 수 없으면 failsafe shutdown을 처리해야 함
- 새 배포로 부모가 교체될 때는 클러스터에서 같은 코드를 실행한다는 보장을 위해 러너도 종료해야 함
- 러너 작업 결과를 기다리는 활성 호출자는 러너가 어떤 이유로든 내려가는 상황을 고려해야 함
- Erlang VM이 제공하는 프리미티브가 이 구현을 단순하게 만듦
- 로컬·원격 프로세스 모니터링과 supervision
- 노드가 올라오고 내려가는 것을 감지하는 node monitoring
- 애플리케이션 시작·종료 순서를 제어해 새 배포 시 활성 러너가 작업을 마칠 시간을 주는 shutdown 흐름
- 내부 구현 세부사항은 별도 글에서 이어질 예정이며, 현재 flame source를 살펴볼 수 있음
현재 상태와 다음 단계
- Elixir FLAME 라이브러리는 아직 초기 단계지만 지금 시도해볼 수 있음
- 향후 더 고급 풀 성장 기법과 Elixir 구현 방식에 대한 심층 분석이 예정돼 있음
- 다른 언어에서 FLAME 패턴을 구현하는 논의도 가능함
댓글과 토론
Hacker News 의견들
-
지난 4년간 100개 넘는 Lambda 함수로 된 앱의 고통과 복잡성을 겪어보니, 이 글이 FaaS 서버리스 아키텍처의 단점을 정확히 찌른다고 봄
처음 시작할 때는 이런 단점이 잘 보이지 않음. 오히려 사용량이 적으면 거의 무료이고 유지보수도 거의 없다는 장점이 뚜렷함
나중에야 Lambda 워크플로가 얽히고설켜 상호 의존성 때문에 점점 경직되면, 차라리 모놀리스로 가고 몇백 달러 더 들여 자체 관리했어야 했다고 후회하게 됨. 요즘은 fly.io 같은 곳이면 더 적게 들 수도 있음
Elixir를 쓰지 않는다면 어떻게 되는지 궁금함- 의욕 있는 개발자들은 거의 어떤 프로세스나 아키텍처든 18개월 정도는 굴러가게 만들 수 있다는 패턴을 계속 봐왔고, 이제는 정리나 법칙에 가까워 보임
상황이 나빠질 때쯤이면 새 직장을 찾을 때가 되기도 함. 특히 입사 후 1년쯤 지나 자신이 도입한 프로세스를 이제 후회하는 경우라면 더 그렇다. 나쁜 상사들, 엉망인 ‘단위 테스트’, 스크럼 등에서 여러 번 봤음
다만 사람들이 직장에서 느끼는 불편함의 원인을 명확히 인식하는지, 아니면 막연히 “이제 떠날 때다”로 받아들이는지는 모르겠음. 불편한 것에 이름을 붙이려 하면 반발을 많이 받았고, Good to Great를 읽은 뒤로는 그에 대한 감정 소모가 훨씬 줄었음. 아무도 “아, 내 행동의 결과구나”라고 말하고 싶어 하지 않음
내가 유지보수하는 루브 골드버그식 시스템을 실질적으로 만든 사람들은 가장 먼저 떠났음. 그 배의 선장이 동료에게 우리 엔진을 오픈소스화하면 어떻겠냐고 물었고, 동료는 이미 존재하고 더 나은 바퀴를 다시 발명한 시스템을 아무도 쓰고 싶어 하지 않을 거라고 답했음. 그 사람은 3~4개월 안에 자진해서 떠났음 - 어떤 언어에서도 이 문제를 풀 수 있을 만한 것을 만들고 있음. 현재는 TypeScript SDK가 있고 Java도 작업 중임
Lambda끼리 서로 호출할 수 있는 일반적인 서비스 지향 코드를 쓸 수 있다면, 애플리케이션을 수백 개의 짧게 실행되는 조각으로 나누지 않아도 됨. 다만 기다리는 시간까지 비용을 내야 한다면 불가능함. Lambda가 입출력 대기 중에는 실행을 일시 중지할 수 있다면 문제가 풀림. 그래서 내구 실행(durable execution) 이 답일 수 있다고 봄
지난 몇 주 동안 이를 보여주는 글을 쓰고 있었음: https://restate.dev/blog/suspendable-functions-make-lambda-t... - 글의 한 섹션에서 Elixir 밖의 FLAME도 다뤘음. 요약하면, 합리적인 동시성 모델이 있는 언어라면 일반적으로 적용 가능한 패턴임
캡처된 변수 직렬화가 되는 함수 같은, Elixir에서 공짜로 얻는 사용성은 전부 얻기 어렵겠지만, JavaScript 같은 언어에서는 클로저로 감싸는 대신 모듈 실행부를 새 파일로 옮기는 식으로 90% 정도는 갈 수 있을 것 같음
FLAME 라이브러리를 구현하는 사람은 풀링, 모니터링, 원격 통신 부분도 작성해야 함. Elixir는 분산 메시징과 모니터링 쪽에서 많은 것을 공짜로 얻음. 프로세스 배치 관련 기능도 사실상 Elixir에만 해당함 - Lambda가 12개만 있어도 견디기 어려웠음. 원래 앱은 유지보수나 배포를 별로 고려하지 않은 사람이 만들었고, 코드가 여기저기 복사-붙여넣기되어 있었음
결국 단일 Lambda가 여러 엔드포인트를 처리하는 뚱뚱한 Lambda 모놀리스로 옮겼음 - 콜드 스타트에 크게 신경 쓰지 않거나 관리할 수 있다면 Lambda에서도 모놀리스를 만들 수 있음
다르게 말하면, 모놀리스로 가면서도 AWS 비용을 최소화할 수 있음. 둘 중 하나만 선택해야 하는 문제가 아님
요즘은 asp.net을 쓰고 있는데, 최적화된 EF 모델을 포함해 ready-to-run으로 배포한 꽤 큰 앱도 비교적 빨리 시작됨
- 의욕 있는 개발자들은 거의 어떤 프로세스나 아키텍처든 18개월 정도는 굴러가게 만들 수 있다는 패턴을 계속 봐왔고, 이제는 정리나 법칙에 가까워 보임
-
글쓴이임. 드디어 공개하게 되어 기쁘고 질문이 있으면 답하겠음. 몇몇 사람이 FLAME 패턴을 JavaScript, Go, 다른 언어로 구현하도록 충분히 자극받았으면 좋겠음
- 좋아 보임. Microsoft가 주목했으면 함. Azure Functions는 보안 설정과 배포가 너무 복잡하고, 어떤 코드를 실행하려는지에 대해 이상한 가정을 깔고 있음
- 가장 자연스러운 구현은 JVM의 vert.x 같은 형태일 것 같음
이미 이벤트 버스 위에서 반응형 확장, Future, 코루틴을 통해 비동기 실행을 처리하는 메커니즘이 있고, 클러스터 전체에 데이터 직렬화와 분산도 지원함. 여러 인기 언어용 이벤트 버스 클라이언트도 있으니, 여러 언어를 섞어 애플리케이션을 만들 수 있을 것임 - 과장은 좀 줄이는 편이 좋겠음
문제를 “…죽음보다 나쁜 운명”처럼 제시하고, 해법은 너무 쉽고 고통이 없어서 다른 접근을 버리지 않으면 바보처럼 보이게 만들면 이런 글은 순수한 영업 글로 치부되기 쉬움. 그건 만병통치약 장사꾼의 방식임
글 초반에 문제 자체는 잘 짚었으니, 훨씬 덜 감정적인 객관적 비교가 있었다면 더 나은 접근을 궁금해하는 사람들에게 거부감이 덜했을 것 같음
글의 실질 내용은 흥미로웠지만 표현 방식 때문에 꺼려졌음. 건설적인 피드백으로 받아들였으면 함 - 글과 영상이 좋고, 개념도 아주 흥미로움. JavaScript 구현을 기대하지만 쉽지는 않아 보임
그리고 ffmpeg.fly.dev를 선점한 게 아주 조금 미안해졌음 - 이것이 Sidekiq가 백그라운드 작업을 실행하려고 Rails 코드베이스를 인스턴스화하는 방식과 근본적으로 어떻게 다른지 궁금함
-
“기존 앱 코드 아무 부분이나 함수로 감싸기만 하면 자동 확장되고, 그 코드 블록이 앱의 임시 복사본에서 실행된다고 상상해보라”는 부분이 흥미로움
fork가 하는 일을 서버리스용으로 만든 것처럼 들림. 훌륭한 작업임 -
몇 년 전에 사실상 이런 일을 해주는 서비스를 쓴 적이 있음. PiCloud는 아쉽게 Dropbox에 흡수됐지만, 그 전에는 작업을 투명하게 워커로 흩뿌리는 정확히 이런 모델을 갖고 있었음. 코드를 묶어서 워커에서 실행하는 방식이었음
예시는 여기 있음. 정확히 같은 모델임을 볼 수 있음: https://github.com/picloud/basic-examples/blob/master/exampl...
Elixir는 써본 적 없지만 수십 년 전 Erlang은 써봤고, BEAM은 근본적으로 많이 바뀌지 않은 것처럼 보임. 설계의 핵심 일부라서 이런 작업에 훨씬 더 적합할 것 같음. 그래도 완전히 공짜 점심은 아닌 게, 기다리는 동안 주 프로세스가 죽을 가능성은 있지 않나 싶음 -
전체 논지에 동의함. 우리는 https://www.windmill.dev에서 다른 접근을 택했는데, 추상화 단위를 컨테이너 수준이 아니라 소스 코드 수준으로 봄
main 함수와 import를 파싱해서 인자와 의존성을 추출한 뒤, 원하는 런타임(TypeScript, Python, Go, Bash)에서 코드를 그대로 실행함. 핵심 비법은 캐시를 효율적으로 관리해서 import와 관계없이 워커가 항상 뜨거운 상태로 유지되게 하는 것임
이 방식은 코드베이스에 FLAME만큼 통합되지는 않지만, 대상 사용자가 다름. 우리 사용자는 복잡한 워크플로, 크론 작업, 또는 자동 생성 UI가 붙은 일회성 스크립트를 처음부터 만듦
FLAME에서는 전체 컨텍스트가 스냅샷되고 대상 VM에서 다시 복원되는 것처럼 보임. 다른 접근은 필요한 컨텍스트와 그렇지 않은 것을 지정하는 문법을 도입해서 최소한만 로드하는 것임. 기존 코드베이스와 Windmill을 더 잘 통합하고 HTTP 호출에 의존하지 않기 위해 현재 탐색 중임- FLAME에서 실제로 일어나는 일은 정확히 그렇지는 않음. FLAME은 BEAM의 내장 클러스터링 기능으로 원격 노드에서 함수를 호출할 뿐임
그 과정에서 필요한 컨텍스트만 전송하는 일이 암묵적으로 처리됨. 글에서도 이렇게 설명함: “FLAME.call은 러너 풀 이름과 함수를 받는다. 그런 다음 전체 애플리케이션의 새 복사본을 찾거나 부팅하고, 그곳에서 함수를 실행한다. 함수가 클로저로 캡처한 변수들, 예를 들어 %Video{} 구조체와 interval은 자동으로 함께 전달된다” - w가 하나 더 들어갔음. 찾는 사람들을 위한 URL은 여기임: https://www.windmill.dev/
프로젝트의 목표가 마음에 듦. Windmill이 더 나은 오픈소스 Retool/Airtable 대안이 되기를 정말 기대함
- FLAME에서 실제로 일어나는 일은 정확히 그렇지는 않음. FLAME은 BEAM의 내장 클러스터링 기능으로 원격 노드에서 함수를 호출할 뿐임
-
“FLAME에서는 개발 및 테스트 러너가 로컬 백엔드에서 그냥 실행된다”는 점이 좋음. 로컬 개발 경험이 괜찮은 서버리스라니 좋다
- 맞음. 서버리스를 좋아하지 않는 이유 중 하나가, 모놀리스를 실행하는 것에 비해 로컬 개발 경험이 훨씬 나쁘기 때문임
-
“그런 다음 전체 애플리케이션의 새 복사본을 찾거나 부팅하고 그 함수가 거기서 실행된다”면,
Flame.call마다 앱 프로세스 전체를 새로 시작하고 실행 컨텍스트를 복사해 넣는 건가?
확장성 측면에서는 매우 단순한 해법이지만 단점도 있을 것 같음
앱 시작 시간이 10ms 늘어나면 애플리케이션의 모든Flame.call지점에 10ms가 추가되는 셈이고, 메모리도 마찬가지일 것 같음
이 시스템을 쓸 때 이런 우려를 고려해야 할 듯함- 글 후반의 FLAME.Pool이 이 부분을 다룸. 러너는 풀링되고, 원하는 시간만큼 설정 가능한 뜨거운 상태로 유지됐다가 유휴 상태로 내려감
부하가 있는 상황에서는 풀이 이미 뜨거워져 있으므로 콜드 스타트 비용을 거의 내지 않음. 다음으로 Elixir 라이브러리에 더 정교한 풀 증가 기법도 추가하고 있어서, 꽉 찬 러너를 만나 새로 콜드 스타트하는 상황도 피할 수 있음
뜨거운 러너에서는 부모와 자식 사이의 지연 시간만 오버헤드임. 같은 데이터센터여야 하므로 1ms 또는 그 이하일 것임
- 글 후반의 FLAME.Pool이 이 부분을 다룸. 러너는 풀링되고, 원하는 시간만큼 설정 가능한 뜨거운 상태로 유지됐다가 유휴 상태로 내려감
-
훌륭함. 우리가 https://www.inngest.com/에서 만든 것의 아주 가벼운 Elixir 전용 버전처럼 느껴짐
둘 다 기존 코드를 무언가로 감싸서 서버리스 함수에서 쓸 수 있게 만들고, 본질적으로 원격 RPC로 호출 가능하게 한다는 점이 비슷함
이런 코드는 종종 명령형 단계들의 연속으로 실행됨. 각 단계는 추가 Lambda로 직렬 또는 병렬 실행될 수 있음. 하지만 단계 사이에 변수에 캡처된 암묵적 상태가 있음. 이 때문에 함수는 워크플로가 됨. Inngest 모델에서는 이 상태를 캡처하고 다시 함수에 주입해서 내구성을 갖게 함
내구성 측면에서 이런 프로세스는 큐를 기반으로 해야 함. 이 모델의 좋은 점은 큐가 저렴하다는 것임. 큐를 한 줄 코드처럼 저렴하게 만들면 모든 것이 쉬워짐. 어떤 개발자든 인프라를 걱정하지 않고 신뢰성 있는 코드를 작성할 수 있음
모니터링과 관측 가능성도 중요함. 데드 레터 큐는 정말 끔찍하고, 실패하는 함수나 단계를 관리하고 재실행할 수 있어야 함
FLAME과 Inngest의 차이도 있음. Inngest는 큐 기반, 이벤트 구동이며, 어떤 언어에서든 HTTP로 제공 가능함. Inngest가 상태를 외부에 저장하므로 Elixir로 워크플로를 작성했다가 TypeScript로 다시 작성하고 재배포해도 실행 중인 함수가 CRIU와 비슷하게 백엔드 언어를 넘나들며 실시간 이전될 수 있음
이벤트 구동 방식이면 흐름 제어도 가능함. 디바운스, 배치, 스로틀링, 팬아웃까지 어떤 런타임이나 언어에서도 다룰 수 있음. 예를 들어 Fly 위의 Elixir 앱 하나가 이벤트를 보내 TypeScript + Lambda에서 함수를 실행하게 할 수 있음
FLAME이 어디로 가는지 기대됨. 목표가 비슷한 부분이 있다고 봄- Inngest는 멋진 서비스처럼 보임. 글에서 작업 처리기, 내구성, 재시도도 다뤘음
Elixir에서 내구성, 재시도, 워크플로가 필요할 때는 보통 Oban을 쓰고, 여기서도 계속 그렇게 할 것임. Oban 작업이 FLAME을 호출해서 탄력적인 실행을 처리하게 됨
- Inngest는 멋진 서비스처럼 보임. 글에서 작업 처리기, 내구성, 재시도도 다뤘음
-
HN이 강제하는 미국식 제목 대소문자 표기를 정말 싫어하는 이유 중 하나임. Serverless가 대문자 S의 회사 serverless.com을 다시 생각한다는 뜻처럼 보이고, 소문자 s의 원칙을 다시 생각한다는 뜻으로 보이지 않음
덧붙이면, 누군가 Serverless를 다시 생각해줬으면 좋겠음- 대소문자 표기는 HN이 강제하는 게 아니라 글을 올린 사람이 정하는 것 아닌가 싶음
https://sst.dev/를 봤는지는 모르겠지만, 그들이 정확히 그 일을 한 것 같음. 예를 들어 Live Lambda Development가 있어서 코드를 클라우드에 올리고 배포를 기다릴 필요 없이 피드백 루프를 크게 줄여 로컬 개발이 정말 쉬워짐
- 대소문자 표기는 HN이 강제하는 게 아니라 글을 올린 사람이 정하는 것 아닌가 싶음
-
아이디어가 꽤 멋지고 API도 훌륭함
“비디오 트랜스코딩 같은 CPU 바운드 작업은 프로덕션에서 전체 서비스를 빠르게 멈춰 세울 수 있다”는 부분은, 그냥 CPU 기준 자동 확장을 하면 안 되는 건가?- 이 생각은 글 초반에서 다루려고 했음. 그런 접근의 문제는 잘못된 작동 수준에서 확장한다는 것임
특정 뜨거운 작업을 처리하려고 웹서버를 포함한 애플리케이션 전체를 확장하게 됨. 우리가 원하는 것, 그리고 FaaS를 찾게 되는 이유는 세밀한 탄력적 확장임. 여기서의 아이디어는 웹서버나 워커 확장 버튼을 마구 누르고 잘 되길 바라는 대신, 기존 앱 코드에 대해 이런 세밀한 확장을 할 수 있게 하는 것임 - 맞기도 하고 아니기도 함. 나머지 워크로드는 CPU를 많이 필요로 하지 않을 수 있음. 한두 개 워크로드에만 이런 성능이 필요하고, 그 작업이 다른 일에 밀려나지 않게 하고 싶을 수 있음
또는 GPU가 필요할 수도 있음
핵심 서비스는 서버 1~2대로 충분하지만, 하루에 한 번쯤만 발생하는 작업을 위해 필요할 때 수십·수백·수천 대로 확장해야 할 수도 있음
- 이 생각은 글 초반에서 다루려고 했음. 그런 접근의 문제는 잘못된 작동 수준에서 확장한다는 것임