회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
출처: 비주얼캐피탈리스트(Visual Capitalist)
회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
기업에서 훌륭한 제품을 만든다면, 결국엔 점점 더 많은 사용자들의 관심을 끌어서 그 제품의 뛰어난 성능과 편리함을 기대하게 만들 것입니다. 이와 마찬가지로 앱도 시간이 지나면서 점점 더 발전하게 되면, 1분 내에 처리해야 하는 요청의 양이 많아지게 됩니다. 이러한 상황에 대한 대비가 되어있지 않다면, 결국엔 앱의 처리 성능이 떨어져서 고객들이 점점 빠져나갈 텐데요. 오늘 위시켓은 여러분께 확장 가능한 앱을 만들 때, 반드시 알고 있어 할 몇 가지 주의사항에 대해 알려드리겠습니다.
시스템에 문제가 발생한다면 CPU나 메모리 용량을 계속 늘릴 수도 있겠지만, 그런 방식은 그저 처리량을 늘리는 것일 뿐이지, 앱 자체의 성능을 개선하는 것은 아닙니다.
다시 말해, 앱의 효율성과 관련한 문제가 발생했을 때, 이런 식의 해결책은 올바른 방식이 아닙니다. 앱을 확장한다는 것은 결코 쉬운 일이 아닙니다. 그렇기 때문에 앱의 규모를 언제 어떤 방식으로 확장할지를 생각하기 전에, 먼저 자신의 앱에 대해서 아주 잘 알고 있어야 합니다.
다음은 지난 20년 동안 인터넷이 어떻게 성장해 왔는지에 대한 간략한 설명입니다.
출처: 비주얼캐피탈리스트(Visual Capitalist)
하지만 그렇다고 해서 레일즈로 만든 앱을 확장하는 작업이 언제나 고통스럽다는 것은 아닙니다. 물론 트위터가 레일즈에서 스칼라(Scala)로 갈아타기는 했지만, 한편으로는 쇼피파이(Shopify)가 지난 약 10년 동안 백엔드에 레일즈를 활용해서 끊임없이 성장해 왔습니다. 쇼피파이의 현재 분당 요청 건수(RPM)는 5만회가 넘으며, 1건의 요청에 대한 반응속도는 0.045초에 불과합니다.
트위터나 쇼피파이처럼 확장성이나 성능과 관련한 이슈가 없다고 하더라도, 어떤 문제가 발생하더라도 적절하게 대처할 수 있도록 어플리케이션을 계획하고 개발하는 것은 매우 중요합니다.
그런데 실제로 앱을 확장하는 작업에 착수하면, 수십 가지의 다양한 문제들을 마주하게 됩니다. 그렇게 흔히 경험할 수 있는 문제들을 몇 가지만 나열하자면 다음과 같습니다.
메모리나 CPU와 같은 물리적인 리소스의 한계
메모리 관리의 잘못
비효율적인 데이터베이스 엔진
복잡한 데이터베이스 스키마(database schema), 잘못된 인덱싱(indexing)
데이터베이스에서 쿼리 처리 성능의 저하
서버 구성(configuration)의 잘못
앱 서버의 한계
프로그램 전체가 스파게티 코드(spaghetti code)로 되어 있음
비효율적인 캐싱(caching)
모니터링 도구의 부족
외부에 대한 의존도가 너무 많음
백그라운드 작업 설계(job design)의 잘못
기타 등등
레일즈는 믿을 수 없을 정도로 거대한 커뮤니티를 갖고 있으며, 수백만 개의 질문에 대한 답변들이 온라인에 올라와 있는 훌륭한 프레임워크입니다. 레일즈에는 수백 개의 뛰어난 오픈소스 도구들이 있기 때문에, 우리는 이러한 도구를 즉시 자신의 기술 스택 안에 포함시켜서 활용할 수 있습니다. 그렇게 함으로써 우리는 각자의 시스템 내부에 있는 장애요소를 확인할 수 있는 수많은 프로파일링 및 분석 도구를 만들어낼 수 있습니다.
이번에도 루비 온 레일즈를 예로 들어서 설명하겠습니다. 여러분의 개발팀이 확장 작업을 보다 효율적으로 하기 위해서는, 다음과 같은 방식을 활용하면 좋습니다.
무턱대고 메모리를 추가하는 데 비용을 낭비하지 말고, 중요항목 표시(bullet point)나 랙-미니-프로파일러(rack-mini-profiler)와 같은 흔한 방식을 사용해서 문제를 찾아냅니다.
동일한 값을 가진 항목들을 다량으로 업데이트할 때는 “전부 업데이트(update_all)” 항목을 사용하거나(이 경우에는 “전부 업데이트”가 콜백(callback)이나 유효성 검증(validation)을 수행하지 않는다는 점을 염두에 두어야 합니다), 맞춤형의 SQL 쿼리를 직접 작성하는 것을 고려합니다. 하나의 루프(loop) 안에 있는 수천 개의 개체에 대해서 업데이트와 같은 자동회귀(Auto Regressive, AR) 기법을 사용하는 것은 좋은 방식이 아닙니다.
ID 대신에 UUID(범용 고유 ID)의 사용을 고려합니다. 순차적으로 증가하는 표준적인 ID를 기본 키(primary key)로 사용하고 있다면, 모든 글쓰기(write) 명령은 대부분의 경우 단일한 데이터베이스를 거쳐서 수행되어야만 합니다. 하지만 UUID를 사용하면, 나중에는 그런 명령들을 수많은 서버에 더욱 쉽게 분산시킬 수 있습니다.
SQL SUM(합계), COPY(복사), DISTINCT(중복제거)와 같은 데이터베이스 레벨의 함수들을 사용합니다. 예를 들자면, ‘Model.sum(&:value)’가 아니라 ‘Model.sum(:value)’과 같은 방식으로 사용합니다. ‘Model.sum(:value)’은 SQL의 합계 기능을 수행해서 그 결과값을 반환합니다. 반면에 ‘Model.sum(&:value)’는 모든 레코드(record)를 가져와서 매핑(mapping)한 다음, 모든 항목들을 일일이 조사해서 ‘value’라는 속성을 가져옵니다. 그런 다음에는 배열(Array)의 합계 기법을 사용해서 우리가 기다리던 결과를 반환합니다. 어느 방식이 더 빠른지 아시겠죠?
다수의 개체들에서 동일한 속성을 가져오려면 ‘map’ 대신에 ‘pluck’을 사용하십시오.
레코드의 수를 셀 때 ‘length’를 사용하지 마십시오. ‘ActiveRecord::Relation#length’라고 쓰면 언제나 모든 레코드를 가져온 다음에 그 결과로 얻은 배열에서 루비(Ruby)의 ‘#length’를 호출하기 때문입니다. 대신에 ‘size’를 사용하면, 필요한 데이터베이스에만 접근하게 됩니다. ‘count’ 역시 싱글 쿼리(single query)이기 때문에 좋은 선택입니다.
사실 앱을 확장하는 데 있어서 마법과도 같은 공식은 없습니다. 모든 애플리케이션들은 모두 각자 다른 고유한 시스템이기 때문입니다.
하지만 앱을 개발하는 동안 더욱 세심하게 주의를 기울인다면, 이후에는 놀라울 정도로 쉽게 확장할 수 있습니다. 그리고 활용할 수 있는 도구들이 아주 많기 때문에, 애플리케이션 확장의 전문가가 될 필요도 없습니다.
대부분의 경우 최적화된 코드, 효율적인 SQL 쿼리, 적절한 데이터베이스(DB) 인덱싱, 그리고 어느 정도의 캐싱 기능만으로 시작해도 충분합니다.
> 이 글은 'How to Effectively Scale Your Web Application'을 각색하여 작성되었습니다.