Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

AppSignal로 완성하는 Ruby on Rails 애플리케이션 커스텀 계측·모니터링 실전 가이드

이번 2부작 시리즈의 첫 번째 글에서는 Ruby on Rails 애플리케이션에 AppSignal을 설정하는 방법을 다루며, 별도의 설정 없이도 즉시 얻을 수 있는 다양한 인사이트를 살펴보았습니다. AppSignal은 에러를 자동으로 추적하고, 성능을 모니터링하며, 일부 의존성에 대한 메트릭까지 보고해 줍니다.

하지만 대부분의 경우 각 애플리케이션은 저마다 다른 방식으로 동작하기 때문에, 범용적인 모니터링만으로는 충분하지 않습니다.

이번 글에서는 Ruby on Rails 애플리케이션에 커스텀 계측(custom instrumentation)과 모니터링을 추가하는 과정을 단계별로 살펴보겠습니다. 이를 통해 애플리케이션이 어떻게 동작하고 있는지 한층 깊은 통찰을 얻을 수 있습니다.

코드를 직접 따라 하려면 다음이 필요합니다:

  • www.appsignal.com 계정
  • 설치 후 실행 중인 Docker (docker-compose 사용)

커스텀 계측과 모니터링

AppSignal이 기본적으로 제공하는 계측만으로는 부족할 때, AppSignal 젬(gem)을 활용하면 Rails 애플리케이션에 원하는 대로 커스텀 계측을 추가할 수 있습니다.

코드의 특정 부분 계측하기

애플리케이션에 새로운 기능을 추가한다고 가정해 봅시다. 사용자가 /posts에 접속해 전체 게시글을 조회할 때, 제목이 특정 문자(혹은 훨씬 더 복잡한 조건 🪄)로 시작하는 게시글만 필터링해서 볼 수 있어야 합니다.

이 검색 기능은 이미 Post 모델의 Post.where_title_starts_with 메서드로 구현되어 있습니다. 특정 쿼리 파라미터가 존재할 때 이 새로운 메서드를 사용하도록 PostsController#index를 수정해 보겠습니다:

# app/controllers/posts_controller.rb
  def index
    starts_with = params[:starts_with]
    @posts = if starts_with.present?
               Post.where_title_starts_with(starts_with)
             else
               Post.all
             end
  end

이 코드는 애플리케이션의 핵심 로직이기 때문에 성능이 어떻게 변화하는지, 그리고 언제 변하는지 반드시 파악하고 싶을 것입니다. AppSignal은 이를 위한 여러 가지 방법을 제공합니다.

먼저 Post.where_title_starts_with 메서드의 내부를 계측해 보겠습니다. 임의의 코드 블록에 대한 인사이트가 필요하다면, 계측 블록(instrumentation block)으로 해당 코드를 감싸면 됩니다. 메서드를 다음과 같이 업데이트하세요:

# app/models/post.rb
def self.where_title_starts_with(letter)
  Appsignal.instrument('Post.where_title_starts_with', "Fetch posts that start with letter") do
    Analytics.track_post_title_search(letter.downcase)
    select('*, pg_sleep(0.01)').where("title ILIKE :letter", letter: "#{letter.downcase}%").load
  end
end

두 번째로, 무거운 처리 작업을 수행하는 app/services/analytics.rb 내부에서 호출되는 Analytics.track_post_title_search 메서드 역시 계측하고 싶습니다. 이런 경우에는 메서드 계측(method instrumentation)을 사용하면 메서드 전체를 더 정확하게 측정할 수 있습니다:

# app/services/analytics.rb
require 'appsignal/integrations/object'
 
class Analytics
  def self.track_post_title_search(letter, sleep = sleep(1))
    # Some heavy processing
    sleep 1
  end
  appsignal_instrument_class_method :track_post_title_search
end

인사이트 확인

위 코드를 저장하고 몇 분 뒤 AppSignal 대시보드에서 새로운 정보가 표시되는지 확인해 보세요. (정보가 보이지 않으면 Docker 컨테이너를 재시작해야 할 수 있습니다.) 검색 파라미터와 함께 게시글 목록 페이지인 https://localhost:3000/posts?starts_with=f 에 접속하면 새 기능이 정상 동작하는지 검증할 수 있습니다.

데이터베이스에 생성된 게시글 수에 따라 /posts 엔드포인트의 응답 속도가 상당히 느려졌음을 알 수 있을 것입니다.

AppSignal의 성능 이슈 화면('Performance' → 'Issue list')에서 PostsController#index 액션을 열고 페이지 아래쪽으로 스크롤하면 'Event Timeline'을 확인할 수 있습니다. 이 타임라인은 특정 코드 실행에 소요된 시간을 상세히 분석해 보여줍니다:

이 타임라인은 모든 성능 이벤트에 존재하지만, 여기에는 커스텀 계측 이벤트도 함께 표시됩니다. 예를 들어 Post.where_title_starts_with 호출에 8.84초가 걸렸으며, 그중 2.01초는 Analytics.track_post_title_search 메서드가, 나머지 시간은 Active Record 쿼리가 사용했다는 것을 확인할 수 있습니다. 개별 이벤트를 클릭하면 추가 조사를 위한 더 자세한 정보 — 예를 들어 sql.active_record 이벤트의 상세 내역 — 도 열람할 수 있습니다.

AppSignal의 계측 헬퍼를 활용하면 애플리케이션 코드를 더 세밀하게 분석할 수 있어, 성능에 영향을 줄 가능성이 있는 특정 코드 조각을 쉽게 파악할 수 있습니다. 자세한 내용은 AppSignal의 공식 계측 가이드에서 확인하세요.

예외 처리

코드 성능을 모니터링하는 것만큼이나, 애플리케이션이 기대와 다르게 동작할 때 그 문제가 어디서 발생했는지 아는 것도 중요합니다. 앞서 AppSignal이 우리 코드에서 처리하지 못한 예외를 어떻게 리포팅하는지 살펴보았습니다. 하지만 기본 제공 기능 외에도 할 수 있는 일이 많습니다.

우선 간헐적인 에러를 유발하던 기존 코드를 제거해 보겠습니다. 대시보드에서 에러를 열면 백트레이스를 통해 어디서 발생했는지 확인할 수 있습니다. app/controllers/pages_controller.rb 안의 if 문을 삭제하세요:

class PagesController < ApplicationController
  def home
    CreateRandomPostsJob.perform_later
  end
end

이제 오버뷰 대시보드에서 애플리케이션의 에러율이 크게 낮아진 것을 확인할 수 있습니다.

현재는 사용자가 존재하지 않는 게시글을 조회하려고 하면(예: https://localhost:3000/posts/doesnotexist ) 애플리케이션이 그대로 크래시됩니다. 대신 친절한 안내 메시지를 보여주는 것이 좋겠죠. 이런 상황이 발생할 수 있는 지점에 rescue를 추가하여 PostsController#set_post 메서드를 수정해 보겠습니다:

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
    .
    .
    .
    private
    def set_post
      @post = Post.find(params[:id])
    rescue ActiveRecord::RecordNotFound => e
      render json: { error: "Oops. That post isn't here" } , status: :not_found
    end
    .
    .
end

예외를 직접 처리하면 AppSignal에 자동으로 리포팅되지 않습니다. 이럴 때 Appsignal.set_error를 사용하면 에러를 수동으로 추적할 수 있습니다.

가장 간단한 방법은 함수의 유일한 인자로 에러 객체를 넘기는 것입니다(Appsignal.set_error(e)). 또한 요청에 추가 컨텍스트를 붙이는 기능도 활용해 보겠습니다. AppSignal은 Appsignal.tag_request를 통해 임의의 정보를 이벤트에 태그로 지정할 수 있습니다:

def set_post
  Appsignal.tag_request(user_id: 'user-from-params', post_id: params[:id])
  @post = Post.find(params[:id])
rescue ActiveRecord::RecordNotFound => e
  Appsignal.set_error(e)
  render json: { error: "Oops. That post isn't here" }, status: :not_found
end

이제 https://localhost:3000/posts/doesnotexist 에 접속하면 애플리케이션이 크래시되는 대신 기대한 JSON 응답을 받는지 확인할 수 있습니다.

인사이트 확인

존재하지 않는 게시글을 조회해 본 뒤에는, 추가된 코드 덕분에 에러가 AppSignal에 리포팅됩니다. AppSignal 대시보드의 'Errors' → 'Issue list'에서 새로 리포팅된 에러(ActiveRecord::RecordNotFound)를 찾아 열어보세요.

에러 상세 페이지는 HTTP 메서드, 파라미터, 세션 데이터 같은 요청 정보를 기본적으로 포함한 유용한 컨텍스트를 제공합니다. 여기에 커스텀 태그도 함께 표시되므로, 동일한 태그를 가진 모든 에러를 필터링할 수 있습니다.

요청에 태그를 달았기 때문에, 이 정보는 에러뿐 아니라 다른 계측된 이벤트에도 함께 기록됩니다. 개별 게시글을 몇 번 조회해 보면(예: https://localhost:3000/posts/1 ), 성능 측정 화면('Performance' → 'Issue list' → 'PostsController#show')에서도 태그가 포함되어 있는 것을 확인할 수 있습니다. 트랜잭션 태깅에 대한 자세한 내용은 공식 가이드를 참고하세요.

트랜잭션에 커스텀 메타데이터를 추가할 수 있다는 점은 프로덕션 환경의 문제를 진단하는 데 무궁무진한 가능성을 열어줍니다. 대표적인 예로, Kubernetes 메타데이터를 에러에 추가하는 사례가 있습니다.

메트릭

커스텀 계측과 에러 모니터링이 갖춰지고 나면, 게시글 검색량에 큰 폭증(spike)이 발생하는 시점이 있다는 것을 눈치챌 수 있습니다. 사용자가 검색할 때마다 Analytics#track_post_title_search가 호출되는데, 이 메서드는 연산 작업을 수행하고 서드파티 서비스에 API 호출을 보냅니다. 그런데 이 서드파티에는 API 요청 속도 제한(rate limit)이 있습니다. 따라서 애플리케이션이 한도에 얼마나 근접했는지 파악하기 위해 호출 빈도를 추적하고 싶습니다.

AppSignal은 애플리케이션 전반에서 원하는 대로 커스텀 메트릭을 추적할 수 있도록 지원합니다.

먼저 카운터(counter)와 태그를 사용해 분석 서비스를 얼마나 자주, 어떤 데이터로 호출하는지 추적해 보겠습니다:

#app/services/analytics.rb
require 'appsignal/integrations/object'
 
class Analytics
  def self.track_post_title_search(letter, sleep = sleep(1))
    Appsignal.increment_counter("track_post_search", 1, { letter: letter })
    # Some heavy processing
    sleep 1
  end
  appsignal_instrument_class_method :track_post_title_search
end

다음으로 PostsController#index에서 반환되는 게시글 수도 추적합니다. 이것은 애플리케이션 핵심 동작의 일부이고, 그 숫자가 계속 늘어난다는 것을 알고 있기 때문입니다:

#app/controllers/posts_controller.rb
class PostsController < ApplicationController
    .
    .
  def index
    .
        .
    Appsignal.set_gauge("posts_index", @posts.size, starts_with: params[:starts_with])
  end
end

애플리케이션에서 계속 실행 중인 가상 트래픽 스크립트가 어느 정도의 데이터를 만들어 주지만, 더 다양한 데이터를 위해 f, l, v로 시작하는 게시글 검색도 직접 수행해 보겠습니다.

인사이트 확인

커스텀 메트릭을 보려면 AppSignal에서 커스텀 그래프가 담긴 대시보드를 먼저 만들어야 합니다. UI를 통해서도 가능하지만, 이번 예제에서는 미리 만들어진 것을 가져와(import) 사용하겠습니다. 'Dashboard' 섹션에서 'Add dashboard'를 클릭하고 아래 내용을 임포트하세요:

{
  "title": "Post Search",
  "description": "Sample dashboard about posts search activity",
  "visuals": [
    {
      "title": "Analytics",
      "line_label": "%name% %letter%",
      "display": "LINE",
      "format": "number",
      "draw_null_as_zero": true,
      "metrics": [
        {
          "name": "track_post_search",
          "fields": [
            {
              "field": "COUNTER"
            }
          ],
          "tags": [
            {
              "key": "letter",
              "value": "*"
            }
          ]
        }
      ],
      "type": "timeseries"
    },
    {
      "title": "Search",
      "line_label": "%name% %starts_with%",
      "display": "LINE",
      "format": "number",
      "draw_null_as_zero": true,
      "metrics": [
        {
          "name": "posts_index",
          "fields": [
            {
              "field": "GAUGE"
            }
          ],
          "tags": [
            {
              "key": "starts_with",
              "value": "*"
            }
          ]
        }
      ],
      "type": "timeseries"
    }
  ]
}

몇 분 안에 그래프에 데이터가 나타나는 것을 볼 수 있습니다. 선 위에 마우스를 올리면 현재 보고 있는 시간대에 수집된 메트릭의 범례(legend)가 표시됩니다.

여기서 주목할 점은 태그 값마다 서로 다른 선이 그려진다는 것입니다. 현재 가상 트래픽은 문자 e만 검색하고 있지만, 우리가 직접 다른 문자들을 검색했기 때문에 각 데이터 포인트마다 새로운 선이 그래프에 추가된 것을 확인할 수 있습니다.

이 정도면 충분하다고 생각하셨나요? 사실 AppSignal에는 이 글에서 다루지 않은 더 많은 커스텀 계측 솔루션이 있습니다. 그중 하나만 간단히 짚자면 브레드크럼(breadcrumbs)입니다. 브레드크럼을 사용하면 애플리케이션 내 일련의 행동(action) 목록을 추적하고, 그 결과가 에러 리포트에 함께 표시됩니다. 이를 통해 에러가 발생하기까지의 과정을 더 구체적이고 순서 있는 정보로 파악할 수 있습니다.

커스텀 계측에 관한 전체 내용은 공식 가이드에서 확인하세요.

마무리: AppSignal로 하는 Ruby 앱 커스텀 계측과 모니터링

시리즈의 1부에서는 Ruby 애플리케이션을 위한 AppSignal의 기본 설정과 사용법을 다루었습니다.

이번 2부에서는 이미 훌륭한 기본 모니터링을 갖춘 애플리케이션을, AppSignal 젬을 활용해 한 단계 더 발전시켜 보았습니다.

AppSignal의 커스텀 계측, 에러 추적, 성능 모니터링 기능은 애플리케이션이 어떻게 동작하는지 이해하는 데 꼭 필요한 인사이트를 제공합니다. 기본적으로 많은 기능을 제공하면서도, 필요할 때는 개발자가 직접 제어권을 가질 수 있게 해준다는 점이 큰 장점입니다.

이제 여러분의 코드를 자유롭게 세상에 내보내도 좋습니다. 물론 그동안 잘 동작하고 있는지 계속 지켜보는 것은 잊지 말고요. 즐거운 코딩 되세요!

P.S. Ruby Magic의 글을 발행 즉시 받아보고 싶다면 Ruby Magic 뉴스레터를 구독하세요. 어떤 글도 놓치지 않을 수 있습니다!