'탄력'에 해당하는 글 1건


프로그래밍 패러다임 관점에서나 탄생 이유의 관점에서나 리액티브(Reactive)를 이해하기 위해서는 현재 개발자와 회사가 직면한 문제들과 10년전의 문제를 비교하여 생각해보아야한다.

개발자와 회사에 바뀐 두가지는 아래와 같다.
  • 하드웨어의 발전
  • 인터넷

"모닥불 주위에 모여 옛날일을 이야기하는 것"은 일부에의해 대화의 수준이 낮아질 수 있음으로 고려해야하는 반면, 우리는 모든 개발자가 직면한 문제를 고심하기 위해 직종의 역사를 탐구해볼 필요가 있다.

왜 이것들이 이제 달라졌을까
10년간 컴퓨팅 연구 끝에 발견한 의미있는 지식이 하나 있다. 리액티브 프로그래밍은 소프트웨어의 새 세대를 만들기 위한 시도이다.

1999년
1999년, 내가 캐나다 임패리얼 상업은행에 있고 처음 자바를 배우기 시작할 무렵이다.
  • 인터넷은 2.8억 유저에 달성했다.
  • JSEe는 여전히 썬(Sun MicroSystems)사의 꿈으로서 가슴속에 있는 상태였다.
  • 온라인 뱅킹이 5년정도 된 유아기 시점이었다.
1999년으로 돌아가면 나는 동시성에 대한 문제에 직면하고 있었다. 그 해결책은 스레드와 락(lock)에 관련이 있었으며, 유경험자의 개발자까지도 그 문제를 해결하기 힘들어하는 상황이었다. 자바의 특징은 "한번 작성하면, 어디서든 실행할 수 있다"인데, 여기서 "어디서든"은 JVM이 설치되어있는 OS에 한에서 "어디에든"이지, 클라우드나 IoT 세대를 위해 설계한 다른 동시접속의 개념이 아니다.

2005년
2005년이 오래전은 아니지만 컴퓨팅쪽과 인터넷쪽이 크게 바뀌었다. J2EE, SOA, XML이 인기를 끌었고 루비온레일즈가 J2EE의 고통받는 컨테이너 기반 개발 문제를 해결하기 위해 탄생하였다.

이때의 인테넷에는
  • 1억유저가 있었다.
  • 페이스북이 550만 유저를 가지고 있었다.
  • 유튜브가 탄생했다.(2005년 2월)
  • 트위터가 아직 없었다.(2006년)
  • Netflix가 비디오 스트리밍을 소개했다.(2007년)

2014년
이 글을 쓰고 있는 시점인 2014년은 Internet Live Stats에 의하면 약 2,950,000,000(29억 5천만)정도의 인터넷 유저가 있다고 한다. 중국이 혼자서 6억 4천만 인터넷 유저를 보유하고 미국이 2억 8천만을 보유하고 있었다.

오늘날 가장 인기있는 웹사이트이다.
  • 페이스북—13억 유저
  • 트위터—2억 7천 유저

시간이 흐르면서 한 웹사이트의 트래픽이 지난 20년전의 인터넷 전체 트래픽보다 많다.

1999년부터 2015년까지 인터넷, 페이스북, 트위터 유저 수1999년부터 2015년까지 인터넷, 페이스북, 트위터 유저 수



우리는 점점 확장과 예측에 관한 이슈가 중요해지고, 우리의 삶에서의 소프트웨어가 중요해짐을 쉽게 확인할 수 있다. 또한 과거의 패러다임이 현재까지 이어질 수는 없을 것이고, 분명 미래까지도 이어지기 힘들 것이다.

4가지 리액티브 요소
리액티브 앱은 4가지 요소로 구성되있다.

  • 반응성(responsive)있는 앱이 그 목표이다.
  • 반응성있는 앱은 확장가능(scalable)하고 탄력(resilient)있다. 반응성은 확장성과 탄력성 없이 불가능하다.
  • 메시지-주도(message-driven) 구조는 확장가능함과 탄력있음을 근간으로 하고 궁극적으로 반응성있는 시스템을 기반으로 한다.

반응성(Responsive)
"앱이 반응성 있다"는 것이 어떤 의미일까?

반응성있는 시스템은 시종일관으로 분명한 유저 경험을 보장하기위해 좋은 상황이나 나쁜  상황이나 상관없이 모든 유저에게 즉각 반응하도록 하는 것이다.

외부 시스템의 실패나 트래픽 급증과 같은 다양한 상황에서의 재빠른 처리분명한 사용자 경험탄력성확장성이라는 두 성질에 의존한다. 메시지-주도 구조는 반응성 있는 시스템을 위한 전반적인 근간을 제공해준다.

왜 반응성 있는 것이 메시지-주도 구조에 중요할까?

세상은 비동기적이다. 당신이 한 포트의 커피를 끓이는데 크림과 설탕이 없음을 깨닭았을때의 그 예시가 있다.

아래는 그 한가지 방법이다.
  • 한 포트의 커피를 끓이기 시작한다.
  • 커피가 끓는동안 가게에 간다.
  • 크림과 설탕을 산다.
  • 집으로 돌아온다.
  • 즉시 커피를 마신다.
  • 여유를 즐기면 된다.

또 다른 방법이다.
  • 가게에 간다.
  • 크림과 설탕을 산다.
  • 집으로 돌아온다.
  • 커피를 끓을 때까지 시계를 보면서 기다린다.
  • 카페인 금단현상을 겪는다.
  • 젠장

여러분이 볼 수 있듯, 메시지-주도 구조는 당신을 시공간으로부터 분리된 비동기 바운더리를 가능하게한다. 우리는 남은 이 포스트에서 비동기적 바운더리 개념에 대해 이야기해볼 것이다.

왈마트 캐나다(Wlamart Canada)에서의 일관성
Typesafe에 들어가기 전에, 나는 왈바트 캐나다의 플랫폼을 만든 Play and Scala 팀의 기술 리더였다.

우리의 목표는 분명한 사용자 경험의 일관성을 만드는 것이었다. 다음 것들에 관계없이 말이다.
  • 데스크탑, 테블릿, 모바일 기기등 어떤 기기에서도 walmart.ca를 서핑할 수 있다.
  • 현재 피크 트래픽이 튀어오르든 유지되든 관계없어야 한다.
  • 전체 데이터 센터의 손실과 같이 주요 기능이 실패해도 관계없어야 한다.

응답 시관과 전반적인 사용자 경험은 위 시나리오와 관계없이 일관성있다. 일관성은 당신의 웹사이트를 전달하는데 근본적으로 중요하며 오늘날 웹사이트는 당신의 브렌드임을 생각해야한다. 좋지않은 사용자 경험은 실제 상점이 아니라 온라인에서 일어나기 때문에 쉽게 잊어지거나 무시되지 않는다.

Glit에서의 반응성있는 소매(retail)
전자상거래 도매인에서의 일관성은 우연에의해 일어나지 않는다. Glit의 경우, 매일 저녁에 하루 세일을 공지하는데, 그 때 트래픽이 급증하는 플래시 스케일 사이트이다. 플래시 스케일 사이트의 사용자 경험에대해 이야기해보자. 만약 당신이 오전 11:58에 한번 Glit를 접속하고 오후 12:01에 한번 더 접속한다면 Glit는 일관성있게 정해진 응답 경험을 제공하고 이것을 리액티브를 사용하여 시행하였다. 스칼라 기반의 마이크로 서비스 구조로 마이그래이션한 Glit를 더 배우고 싶다면 interview with Eric Bowman of Gilt 여기를 보아라.

탄력있는(Resilient)
많은 앱들이 이상적 환경만을 고려하면서 설계, 개발하지만 사실 이상적이지 않을때도 많다. 이것은 매일 주요앱 기능이 실패하는 다른 보고서를 받거나, 해커에의해 서비스 멈춤, 데이터 손실, 지속적인 손상을 초례하는 다른 co-ordinated breach 시스템이 있을 수 있다.

탄력있는 시스템은 이상적이지 않는 상황에서도 반응성을 보장하기 때문에 바람직한 설계 요소와 구조 요소를 사용한다.

자바와  JVM은 다중 OS에서 한 앱을 문제없이 실행시키는 것에 대한 것이었다면, 201x년대의 상호연결된 앱은 앱단의 구성, 연결성, 보안성에 대한 것이다.

이제 한 앱은 웹사이트나 다른 네트워크 규약을 통해 통홥되어 여러 앱으로 구성되어있다. 오늘날 하나의 앱은 신뢰된 방화벽을 가진 바깥은 외부 서비스들에(10개, 20개, 혹은 더 많이) 의존하며 만든다.

이 복잡한 통합을 고려하면 얼마나 많은 개발자가 필요할까?

  • 모든 외부 의존성을 분석하고 모델링하는 사람
  • 통합된 각 서비스의 이상적인 응답 시간을 문서로 만들고 피크일때나 아닐때 모두 퍼포먼스 테스트가 초창기 기대와 일치하는지 확인하기위해 관리하는 사람
  • 모든 퍼포먼스, 실패, 핵심앱 로직으로 포함되는 다른 비기능적인 요구사항을 문서화하는 사람
  • 각 서비스의 모든 실패 시나리오를 다시 분석하고 테스트하는 사람
  • 외부 의존성의 보안성을 분석하고, 외부 시스템과 통합했을때 새로운 취약점이 있는지 알아내는 사람

탄력성은 가장 정교한 앱의 가장 약한 연결 중 하나이지만, 추가적으로 탄력있어야하는 것은 곧 끝난다.(원문: Resiliency is one of the weakest links of even the most sophisticated application, but resiliency as an afterthought will soon end.) 현대의 앱들은 이상적인 상황 보다는 다양한 현실세계에서 반응성을 유지해야하기 때문에, 앱의 핵심(core)이 반드시 탄력적이어야한다. 퍼포먼스, 내구성, 보안성은 모든 면에서 말이다. 여러분의 앱은 단지 몇 부분이 아닌 모든 부분에 걸쳐 탄력적이어야한다.

메시지-주도 탄력성
메시지-주도 핵심에서 가장 아름다운 제작방법은 여러분이 자연스럽게 작업에 필요한 것들을 한조각 한조각 얻어내는 것이다.

고립(Isolation)은 시스템의 자체 회복을 위해 필요하다. 잘 고립되있을때, 우리는 실패의 위험이나 퍼포먼스 특징, CPU와 메모리 사용 등과 같은 요인에 기반하여 여러 타입으로 나눌 수 있다.

정확한 위치는 마치 같은 VM에서 동작하게 한 것 처럼 서로다른 노드위에서 서로다른 프로세스들이 상호소통할 수 있게 해준다.

특정 목적용 에러 채널은 단지 에러 신호를 호출자에게 던저버리는게 아니라 다른 우리가 원하는 곳으로 보낼 수 있다. 

이러한 사실은 우리 앱에서 확고하게 에러 핸들링을 구체화하고 결함에 내성을 만들어준다. 이것은 아카의 감독 계층(Akka's supervisor hierarchies)처럼 구현함으로서 입증되었다.

조각(block)을 만드는 핵심은 이상적인 환경 뿐만 아니라 좋지않은 환경에서도 메시지-주도 구조가 탄력성에 기여하는 것에의해 제공하고 다음으로 반응성에 기여한다.

44억 달러의 탄력성 실수
2012년에 를 생각해보자. 소프트웨어가 향상되는동안 통합된 앱 방식은 점점 인기있고(fired up) 거래 규모가 점점 커지기 시작했다.

다음은 45분동안 일어나는 악몽같은 시나리오였다.

Knight의 자동교환 시스템이 잘못 거래하여 나스닥(NASDAQ)을 침수시켰고, 10억달러가치를 의도치않은 회사에 놓았다. 이러한 사고는 다시 반환(reverse)하는데 회사에 44억달러의 비용이 들게 되었다. 나스닥이 침수되고 거래의 범람을 고치는 동안 나스닥은 Knight 돕는 것을 중단했다. Knight의 주식은 하루만에 63%나 떨어졌으며 그들은 가까스로 살아남았고, 일부를 회복한 후에 투자자에의해 다음 인계를하고 살아갈 수 있었다. 

그때 Knight의 시스템은 동작했었지만 탄력성이 없었다. 탄력성이 없는 Knight 시스템은 문제를 확장시키는데 한 몫을 하였다. Knight는 최악의 버그가 발생했을때 그들의 시스템을 끌 수 있는 스위치(kill-switch) 매커니즘도 없는 상태였으므로, 나쁜 환경에서 그들의 자동 거래시스템이 45분만에 회사의 모든 주요 자산을 고갈시켜버린 것이다.

이것은 이상적인 환경을 위한 설계 정의이자 개발 정의였다. 이제 소프트웨어는 우리 개인 삶이나 회사에서 핵심 요소이다. 예측되지 못한 나쁜 상황의 시나리오 된다면 굉장히 많은 비용을 감당해야할 수 있다.

확장가능한(Scalable)
일관성있는 반응성 앱을 만들때 탄력성과 확장성을 잘 이용해야한다.

확장 가능한 시스템은 다양한 요구량의 상황(various load conditions)에서도 반응성을 보장하기 때문에 그에 맞춰 쉽게 업그레이드 시킬 수 있다.

온라인에서 물건을 팔아본 사람이라면 물건을 최대로 많이 팔때 가장 큰 트래픽이 생긴다는 사실을 알 것이다. 대부분의 경우(사이버상 공격을 제외하고) 트래픽이 폭발하는 것은 당신이 뭔가 잘하고 있을 때이다. 트래픽이 치솟았을대 사람들은 당신에게 돈을 주고 싶어하고 있는 것이다.

그럼 어떻게 치솟는(혹은 꾸준히 증가하는) 트래픽을 다룰까?

첫째로 당신의 패러다임을 먼저 고른다. 둘째로 그 패러다임을 구현할 수 있는 언어와 툴킷을 정한다. 많은 개발자가 종종 너무 가볍게 언어와 프레임워크를 선택한다. 한번 툴을 선택하면 그것을 다시 바꾸기 쉽지 않으므로 당신은 주요 투자가와 함께 그 결정을 내려야한다. 만약 여러분이 기술적인 선택 결정을 원칙과 분석에 기반하여 하고 있었다면 굉장히 잘하고 있는 것이다.

동시성을 위한 스레드-기반 제한
기술적인 선택에서 가장 중요한 것중 하나는 동시성 모델 프레임워크이다. 고수준에서 두개의 서로다른 동시성 모델이 있을 수 있다.
  • 전통적인 스레드-기반 동시성으로 콜스택과 공유 메모리를 기반으로 한다.
  • 메시지-주도 동시성

레일즈와같은 몇 인기있는 MVC 프레임워크는 스레드 기반이다. 이 프레임워크의 전형적인 특징은 아래와 같다.
  • 공유 가변 상태(Shared mutable state)
  • 요청당 한 스래드(A thread per request)
  • 가변 상태에 동시 접근(Concurrent access to mutable state)—이것(변수나 객체 인스턴스)은 또다른 복잡한 동기화 방법이나 락(lock)으로 관리한다.

루비와 같은 인터프리트 언어로 다이나믹 타입의 특징을 합치면 여러분은 쉽게 퍼포먼스와 확장성의 상한선에 도달할 수 있을 것이다( Combine those traits with a dynamically typed, interpreted language like Ruby and you can quickly reach the upper bounds of performance and scalability). 이것이 어떠한 스크립트 언어라도 그 본질은 같다고 말할 수 있을 것이다.

Out 혹은 Up?
앱 확장을 좀 다른 방법으로 생각해보자.

스케일업(Scale up)은 단일 CPU/서버의 리소스를 최대화 하는 것인데, 파워풀하고 희귀하고 값비싼 그런 컴퓨터를 종종 사야한다.

스케일아웃(Scale out)은 여러 저렴한 하드웨어를 연결하여 컴퓨테이션을 제공하는 것인데, 비용면에서 효과적이다. 그러나 당신의 시스템이 시공간 개념에 기반하였다면 매우 어려울지도 모르겠다. 위에서도 이야기했듯 메시지-주도 구조는 시공간으로부터 분리하기위해 필요한 비동기 바운더리를 제공하며, 필요에따라 스케일아웃 할 수 있는 유연성(elasticity)을 제공한다. 반면 스케일업은 이미 가지고있는 자원의 효율을 높히는 것이고, 유연성은 당신의 시스템이 바뀌기 원하는대로 새 자원을 추가할 수 있음에 관한 것이다. 필요에따라 스케일아웃 할 수 있는 능력은 리액티브 앱의 궁극적인 확장성의 목표이다.

공유 가변 상태, 스레드, 락 기반의 앱을 스케일아웃하는게 어렵기 때문에 리액티브 앱들을 스레드 기반으로 만드는것은 어려운 일이다. 개발자들은 한 머신에서 멀티 코어의 이점을 활용해야 할 뿐만 아니라, 특정 시점의 개발자들은 머신의 클러스터를 활용해야 한다. 그게 불가능할지라도, 공유 가변 상태 또한 스케일업하기 어렵게 만든다. 한번이라도 두개의 스레드에서 공유 가변 상태를 다뤄본 사람이라면, 스레드 세이프를 보장하는 과정이 얼마나 어려운지, 스레드 세이프를 위해 과한 작업을 하게되는 실적 패널티가 얼마나 큰지 이해할 수 있을 것이다.

메시지-주도(Message-driven)
메시지-주도 구조는 리액티브 앱의 근간이다. 메시지 주도 앱은 이벤트 주도 이거나 행위자 기반일 것이고, 혹은 이 둘 모두를 합친 것일 것이다.

이벤트 주도 시스템은 0개 혹은 그 이상의 Observer에의해 관찰된 이벤트 기반이다. 이것은 명령형 프로그래밍과는 좀 다른데, 호출자가 부른 루틴으로부터 응답을 블락된 상태로 기다릴 필요가 없기 때문이다. 이벤트는 바로 특정 장소를 지정하는게 아니라 나중에 일어날 어떤 결과를 지켜보고 있는 것이다.

행위자-기반 동시성은 메시지-전달 아키텍처의 확장이며 메시지는 수신자에게 전달된다. 메시지는 스레드 경계를 넘거나 실제 다른 서버의 다른 행위자의 메일함으로 전달 될 수 있다. 행위자가 네트워크를 통해 배포 될 수 있지만 여전히 동일한 JVM을 공유하는 것처럼 서로 통신 할 수 있으므로 요구에 맞게 스케일-아웃 할 수 있다.

메시지와 이벤트의 차이점은 메시지는 전송되는 것이고 이벤트는 일어나는 것이다. 메시지는 명확한 도착지가 있지만 이벤트는 0혹은 그 이상(0-N)의 Observer에의해 관찰되고 있을 것이다.

이벤트-주도와 행위자-기반 동시성에 대해 좀 더 세부적으로 들어가보자.

이벤트-주도 동시성
일반적인 앱들은 명령형 스타일(오퍼레이션 순서)로 개발되고 콜스택을 기반으로 개발한다. 콜스택의 주 기능은 프로세스에서 호출자가 블럭되고 리턴값과 한께 호출자에게 컨트롤을 돌려주는 동안, 주어진 루틴에서 호출자를 계속 쫓고, 호출된 루틴을 실행하는 것이다.

겉으로 보았을 땐 이벤트 주도 앱이 콜스택에 맞추는게 아니라 이벤트 트리거에 맞춘다. 이벤트는 0개 혹은 그 이상의 Observer에의해 지켜보고 있는 큐에 메시지로 인코딩 되어 있을 것이다. 명령형 스타일과 비교하여 이벤트-주도의 큰 차이점은 응답을 기다리는 동안 호출자가 한 스레드 위에서 블락되거나 멈추지 않는다. 이벤트 루프 자체는 단일 스레드 일 수 있지만, (단일 스레드 이기도 한)스레드 된 이벤트 루프가 들어오는 요청을 처리 할 수 있도록 허용하면서 호출 된 루틴이 업무를 수행하는 동안 (잠재적으로 IO 자체를 차단하면서) 동시성은 여전히 달성된다. 프로세스가 완전히 끝나지 않는한 요청을 블락시키는 대신에 호출자의 id가 요청 메시지의 바디와 함께 전달되므로 깨어있는 루틴이 이것을 선택하면 호출자는 응답과 함께 콜백될 수 있다. 

이벤트 주도 구조를 선택하는 이유는 콜백지옥(링크)이라는 것에 괴로워할 수 있기 때문이다. 콜백지옥은 메시지를 받는 놈이 정해져 있는 것이 아니라 익명의 콜백이기 때문에 발생한다. 콜백지옥을 해결하는 일반적인 방법은 이러한 문제가 생기는 이유를 잊고 코드에 표현된 이벤트 순서대로 디버깅하기 어려운것도 생각하지 않으면서 온전히 구문(aka, the Pyramid of Doom) 형태에만 초점을 맞춘다.

행위자-기반 동시성
행위자-기반 앱은 여러 행위자 사이에서 비동기 메시지를 보낸다.

행위자는 아래 속성들을 갖는다.
  • 메시지를 받기 위한 메일 박스
  • 각 타입별로 메시지를 어떻게 받는지 결정하기 위해 패턴매칭의 행위자 로직
  • 요청 사이에 컨텍스트를 저장하기 위한 고립된 상태
이벤트-주도 동시성처럼 행위자 기반 동시성에서는 콜스택은 피하고 가벼운 메시지 전달을 지향한다. 행위자는 메시지를 밖으로든 자기자신에게든 보낼 수 있다. 한 행위자는 그 큐의 첫번째 요청을 먼저 제공한 뒤에 처리가 긴 요청을 처리하기 위해 메시지를 자기자신에게 보낼 수도 있다. 행위자-기반 동시성의 큰 장점은 이벤트-주도 구조에서 얻은 장점을 얻을 수 있다는 점이다. 네트워크 경계를 통해 컴퓨테이션을 스케일-아웃하기도 쉽고, 행위자에게 직접 메시지를 전해주기 때문에 콜백 지옥을 피할 수도 있다. 이러한 강력한 컨셉은 설계, 구현, 유지보수하기 쉬우면서 확장성이 있는 앱을 만들 수 있게 해준다. 시공간에대해 생각하거나 깊게 감쌓인 콜백들에대해 생각하는것보다, 행위자 사이에 메시지 흐름이 어떻게 되는지만 고민하는게 더 낫다.

또 다른 주요 장점은 요소들끼리 느슨하게 연결된다는 점이다. 호출자는 응답을 기다리기 위해 스레드를 멈추지 않으므로 빨리 다른 일을 할 수 있다. 호출자에의해 켭슐화되있는 현재 루틴은 필요에따라 호출자를 다시 호출하면 된다. 이것은 다양한 가능성을 열어준다. 콜백 스택이 한 메모리 공간에 있기 위해 앱을 묶어버리지 않으며 행위자 모델은 프로그래밍적인 일보다 구성에 관련된 일을 추상화하여 만듦으로 여러 머신을 통해 루틴을 분배할 수 있다.

Akka는 행위자-주도 툴킷이고 타입 세이프 리액티브 플랫폼의 일부로서 JVM에서 높은 동시성, 분배, 실패에 대한 내성을 가진 행위자-기반 앱을 만들기 위한 런타임이다. Akka는 탄력을 위한 관리계층이나 확장성을 위한 일 분배와 같은 리액티브 앱 개발에 필요한 멋진 기능들을 탑재하고 있다. Akka에대해 더 깊게 보고싶으면 Let it Crash blog를 확인해보길 바란다.

또한 이 세션 일부의 자료로 사용된 Benjamin Erb’s Diploma Thesis의 글(Concurrent Programming for Scalable Web Architectures)을 읽어보기를 강력 추천한다.

결론
위의 모든 것들이 오늘날 앱 개발에 흠집을 내고, 왜 리액티브 프로그래밍이 단지 또다른 트렌드가 아닌지 이야기하며, 그러나 왜 현대 소프트웨어 개발자들이 배워야하는 패러다임인지도 이야기했다. 여러분이 선택하는 언어나 툴킷에 관계없이 반응성을 얻기위해 확장성과 탄력성 또한 탑재하는 것이 사용자의 기대를 충족시키는 유일한 방법이다. 이것은 몇년간 더욱 중요하게 떠오를 것이다.

저자에대해
Kevin Webber는 Lightbend에서 Enterprise Advocate이다. 그는 heritage 구조에서 리액티브 프로그래밍 원칙을 포괄하는 실시간 분배 시스템까지 큰 구조의 트랜지션을 돕는 것에 열정적이다. 남는 시간에 ReactiveTOProgramming Book Club Toronto를 운영한다. 그는 가끔 제 3자에서 자기 자신에 대해 글을 쓰기도 하는데, 이 단락이 그러한 순간이다. 


'그 외' 카테고리의 다른 글

[번역] 리액티브 프로그래밍이란?  (0) 2017.03.03
(번역)Android Architecture  (0) 2016.10.01

WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

트랙백  0 , 댓글  0개가 달렸습니다.
secret