애플리케이션 디자인

어떤 가공도 하지 않는 것이 약간의 가공을 거치는 것보다 더 나을까?

9 분 읽기

작은 사고 실험을 해봅시다.

그래, 알아, 생각 말이야. 누가 그런 걸 하고 싶겠어?

잠깐만요!

지루해하며 섹시한 인덱스에 관한 다음 게시물로 넘어가기 전에

최소한 며칠은 아니더라도 몇 분만이라도 시간을 주세요.

웹사이트가 하나 있다고 가정해 봅시다.

글쎄요, 아무 웹사이트나 그런 것은 아니고요...

그건 너무 진부하네요.

좋습니다, 여행 웹사이트가 있다고 가정해 봅시다.

사람들이 항공권을 예약하기 위해 오는 곳.

우리 모두 한 번쯤은 그런 걸 써본 적이 있을 거예요.

그러니까, 사용자들이 여러분의 사이트를 방문해서 어떤 항공편이 있는지 확인하고 싶어 합니다.

그들이 가장 먼저 하는 일이 무엇인가요?

그들은 질문을 손글씨로 직접 쓰나요?

요즘은 아니에요.

아마도 출발지를 정하는 것부터 시작할 것입니다.

그곳의 지역 공항.

그리고 아마 가고 싶은 곳을 선택하고 싶어 할 겁니다.

자, 두 가지 선택지가 있습니다, 둘 다 공항입니다.

주변에 어떤 공항들이 있는지 추측하게 할 수도 있습니다.

내 말은, 난 보통 그냥 가야 할 소도시를 입력하고 웹사이트가 내가 어떤 공항으로 비행기를 타고 가야 하는지 알아서 찾게 둔다는 거지.

하지만 사용자들이 이미 여행의 출발지와 도착지에 있는 공항을 알고 있다고 가정해 봅시다.

이 덕분에 우리의 작은 사고 실험을 쉽게 진행할 수 있습니다.

그러니 사용자가 선택할 수 있도록 공항 목록을 표시해야 합니다.

그래, 목록이 꽤 길 거야.

이 넓고도 거대한 세상 곳곳에 공항이 정말 많이 흩어져 있는 것 같네요.

여행용 샘플을 담는 바구니에 담기만 해도 거의 2천 개가 됩니다.

와, 공항이 정말 많네!

누군가 데이터를 엄청나게 많이 입력했네요…

하지만 공항의 좋은 점은 그렇게 자주 변하지 않는다는 것이다.

뭐, 물론 새로 지어지고 있는 것들도 있긴 하지만…

그리고 오래된 것들은 폐허로 방치되고…

하지만 그 모든 것은 시간이 지나면서 일어납니다.

보통 한 공항이 폐쇄되는 이유는, 대개 더 새롭고 멋진 공항이 지어졌기 때문인 경우가 많습니다.

게다가 새로운 공항을 짓는 데는 오랜 시간이 걸립니다.

그것들을 매일 토해내는 것도 아니잖아.

자, 그럼 다시 공항 목록으로 돌아가서…

길든 짧든, 사용자가 선택할 수 있도록 어떤 형태로든 공항 목록을 제공해야 합니다.

그리고 웹사이트 접속량이 매우 많다면, 많은 사용자가 있을 수 있습니다.

그리고 우리 모두는 우리 웹사이트에 많은 방문자가 몰리기를 원합니다.

그럼 우리 웹사이트가 단순히 바쁜 것뿐만 아니라…라고 가정해 봅시다.

정말 바쁘네요.

매일 수백만 명의 사용자.

매분 수천 명의 사용자가 이용하고 있습니다.

그 공항 목록을 그렇게 자주 제공해야 하다니!

그럼, 우선 Couchbase 버킷에 있는 공항 관련 문서의 구조가 travel-sample 버킷에 있는 문서와 같다고 가정해 봅시다.

이건 우리 Couchbase Server 제품에 기본으로 포함되어 있으니, 그냥 써보는 게 어때요!

일이 훨씬 수월해지네요…

자, 간단한 N1QL 쿼리를 사용해 공항 목록을 출력해 보겠습니다:

SELECT `travel-sample`.*;```
FROM `travel-sample`;```
WHERE type = "airport";```
;

이것을 우리에게 주십시오:

[
 {
  "airportname": "칼레 뒁케르크",
  "city": "칼레",
  "country": "프랑스",
  "faa": "CQF",
  "geo": {
    "alt": 12,
    "lat": 50.962097,
    "lon": 1.954764
  },
  "icao": "LFAC",
  "id": 1254,
  "type": "공항",
  "tz": "Europe/Paris"
 },
 {
  "airportname": "페론 생캉탱",
  "city": "페론",
  "country": "프랑스",
  "faa": null,
  "geo": {
    "alt": 295,
    "lat": 49.868547,
    "lon": 3.029578
  },
  "icao": "LFAG",
  "id": 1255,
  "type": "공항",
  "tz": "Europe/Paris"
 },
...
]

음, 이 안에서 우리 사용자들이 필요로 하는 걸 찾는 게 쉽지는 않겠네요. 아마 FAA 공항 코드로 정렬한 다음, 코드가 null인 항목들을 제거하면 될지도 몰겠어요...

[
 {
  "airportname": "랜즈다운 공항",
  "city": "영스타운",
  "country": "미국",
  "faa": "04G",
  "geo": {
    "alt": 1044,
    "lat": 41.1304722,
    "lon": -80.6195833
  },
  "icao": null,
  "id": 8534,
  "type": "airport",
  "tz": "America/New_York"
 },
 {
  "airportname": "모턴 필드 시립 공항",
  "city": "터스키기",
  "country": "미국",
  "faa": "06A",
  "geo": {
    "alt": 264,
    "lat": 32.4605722,
    "lon": -85.6800278
  },
  "icao": null,
  "id": 8317,
  "type": "airport",
  "tz": "America/Chicago"
 },
...
]

그게 낫긴 하지만, 웹사이트에 제공해야 할 것보다 더 많은 데이터입니다.

자, 우리가 반환하는 항목을 FAA 코드, 공항 이름, 도시, 국가로 줄여봅시다:

[
 {
  "airportname": "랜스다운 공항",
  "city": "영스타운",
  "country": "미국",
  "faa": "04G"
 },
 {
  "airportname": "모턴 필드 시립공항",
  "city": "터스키기",
  "country": "미국",
  "faa": "06A"
 },
...
]

자, 이제 드디어 우리가 찾던 것에 접근하고 있네요.

그럼 이걸 조회하면, 어, 응답 시간이 대략 5~60밀리초 정도 나온다는 거네요.

나쁘지 않네.

그러나 이 목록에 대한 요청이 1분마다 수천 건씩 들어오기 때문에…

음, 속도를 조금 더 높일 수 있을지도 모르겠네요.

필요한 모든 것을 포함하는 자체 인덱스를 추가하여 커버드 쿼리로 만들어 봅시다.

CREATE INDEX myFaaIndex on `travel-sample`(faa asc,airportname,city,country)
WHERE type = "airport" AND faa IS NOT NULL;

그리고 이제 쿼리를 다시 실행하면 약 17.5밀리초의 응답 시간을 얻을 수 있습니다.

훨씬 낫네요.

하지만 이보다 더 잘하는 것이 가능할까요?

내 말은, 이 목록이 1분에 수천 번씩 요청될 거라는 거야.

그 수밀리초들이 쌓이게 될 것이다.

그렇다면 이 쿼리의 결과를 단일 문서로 저장하면 어떨까요?

“airport_list”라고 부릅시다.

이제 “USE KEYS” 절을 사용하여 전체 문서를 선택하는 쿼리를 실행하면:

SELECT `travel-sample`.*
FROM `travel-sample`
USE KEYS "airport_list";

이로 인해 응답 시간은 약 14.5ms가 됩니다.

흠, 또 전체 3밀리초를 절약했군!

그리고 데이터 서비스에서 ID로 문서를 직접 가져오는 키-값 접근 방식을 사용하면 0.5밀리초나 2밀리초 정도를 더 단축할 수 있을지도 모릅니다.

1분에 수천 번 제공되어야 하는 문서를 위해.

하루에 수백만 번.

그 수밀리초들이 쌓이게 될 것이다.

그래, 나도 알아. 공항은 수시로 바뀌니까.

그래요, 하지만 그것들은 자주 바뀌지 않아요.

예, 이 문서 하나는 주기적으로 교체해야 합니다.

하지만 그것은 활성도가 높은 웹사이트를 지원하는 작업이 아닙니다.

그 과정이 상대적으로 얼마나 느릴지는 누가 신경 쓰겠어요.

게다가 더 이상 커버링 인덱스도 필요 없어!

인덱스 서버에서 공간을 조금 절약할 수 있게 되었어요!

우와! 보너스다!

그래, 나도 알아. 별난 것들에 신이 난다니까…

좋습니다, 그것은 응답 시간에서 밀리초를 줄이는 연습이었습니다. 조금 더 시간이 걸리고 더 많은 작업을 수행하는 쿼리는 어떨까요?

콜센터를 운영하고 있고, 상담원들이 걸려오는 전화를 얼마나 빨리 받는지를 추적하는 것이 중요하다고 가정해 봅시다...

좋습니다, 좀 더 구체적으로 들어가 보죠.

예를 들어, 오늘 5초 이내에 응답된 통화 수, 10초 이내에 응답된 통화 수, 그리고 오늘 들어온 총 통화 수를 보여주는 대시보드를 만들고 싶다고 해봅시다.

이런 식의…

SELECT SUM(five) as fiveCount, SUM(ten) as tenCount, SUM(incoming) as callCount
FROM
 (SELECT
    CASE 
      WHEN connectTime = 0 AND (endTime - startTime)  0 AND (acceptTime - startTime)  0 AND (acceptTime - startTime)  $today
    AND callType BETWEEN 10 AND 2000) as calls
;

그래서 startTime과 callType 속성에 인덱스를 생성하고 “cdr” 유형의 문서로 제한하여 이 쿼리를 실행해 보면 약 1초가 걸린다는 것을 알게 됩니다.

그리고 이것이 대시보드를 채우는 데 사용하고 싶은 유일한 쿼리는 아닙니다…

아, 이거 엄청 답답하게 느릴 거네!

좋습니다, 이제 모든 속성이 포함된 새 인덱스를 구축하여 커버드 쿼리(covered query)로 만들었는데, 향상되기는 했지만 여전히 약 100밀리초가 걸린다는 것을 알 수 있습니다.

이것 봐요, 무려 10배나 향상되었어요! 정말 멋지지 않나요?

당신의 대시보드만 여전히 당밀 속을 걷는 것처럼 느리게 새로고침됩니다.

얇고 묽은 당밀이지만, 그래도...

Hmm, what can we do to improve this?

What if, instead of using this query to feed the dashboard, we take the output and use it to create a new document with just the results?

Something with a known name, like call_stats_<some date>…

And we can run this query on a timer, using a cron job, or trigger it using the Couchbase Eventing service.

Only if we trigger it from the Eventing service, we probably want to run it with a scan consistency of at least at_plus to include the document update you are using to trigger the query.

But now, when we retrieve the result document, we’re achieving response times in the low single-digit milliseconds, so close to a 1000X improvement in performance!

And now we’ve got a responsive dashboard!

WOO-HOO!!!

Now we’re talking turbo-booster speed!

So, what is the lesson from both of these two scenarios?

Well, by taking any processing we needed to do on the data and making them background tasks, so that our interactive data requests involve no processing, we’ve made things very speedy…

We’re talking faster than a speeding bullet fast!

Excuse us Superman, we’re coming through…

So, was that thought experiment really that painful?

Now on to those sexy indexes…

Couchbase, empowering data nerds everywhere…

(Hey Peter, I think I’ve got our new slogan here!)

이 기사 공유하기

작가

Davis Chapman calls himself a Solution Architect, claims to be employed by Couchbase, and is supposedly part of our Professional Services team. He says that he’s been in the industry for decades, and has been involved in application development for most of that time. Hmm, we’ll have to check on that…

댓글 남기기

카우치베이스 카펠라를 시작할 준비가 되셨나요?

개발 시작하기

NoSQL을 탐색하고, 리소스를 찾아보고, 튜토리얼을 시작하려면 개발자 포털을 확인하세요.

카펠라 프리 사용하기

단 몇 번의 클릭으로 카우치베이스(Couchbase)를 직접 체험해 보세요. Capella DBaaS는 시작하기 가장 쉽고 빠른 방법입니다.

연락해

Couchbase 제품군에 대해 더 알고 싶으신가요? 저희가 도와드리겠습니다.