간단한 사고 실험을 해보겠습니다.
네, 저도 알아요. 누가 그러고 싶어할까요?
잠깐만요!
섹시 인덱스에 대한 다음 포스팅을 찾아보기 전에 먼저...
몇 분만 시간을 주세요.
웹사이트가 있다고 가정해 보겠습니다.
글쎄, 그냥 웹사이트가 아니라...
너무 일반적인 표현입니다.
여행 웹사이트가 있다고 가정해 보겠습니다.
사람들이 항공 예약을 하러 오는 곳입니다.
우리 모두 한 번쯤은 사용해 본 적이 있을 것입니다.
따라서 사용자는 사이트를 방문하여 어떤 항공편이 있는지 확인하려고 합니다.
그들이 가장 먼저 하는 일은 무엇인가요?
질문을 장문으로 작성하나요?
요즘은 그렇지 않습니다.
출발지를 선택하는 것부터 시작할 수 있습니다.
현지 공항.
그런 다음 어디로 가고 싶은지 선택하려고 할 것입니다.
따라서 두 공항, 두 가지 선택이 가능합니다.
주변에 어떤 공항이 있는지 맞히게 할 수도 있습니다.
보통은 가고자 하는 도시를 입력하면 웹사이트가 어떤 공항으로 비행기를 타야 하는지 알아서 알려줍니다.
하지만 사용자가 여행의 양쪽 끝에 있는 공항을 이미 알고 있다고 가정해 보겠습니다.
간단한 사고 실험을 쉽게 할 수 있습니다.
따라서 사용자에게 선택할 수 있는 공항 목록을 제시해야 합니다.
네, 목록이 길어질 겁니다.
이 넓은 세상에는 많은 공항이 흩어져 있는 것 같습니다.
여행용 샘플 버킷을 채우는 것만으로도 2,000개에 가까운 샘플을 얻을 수 있습니다.
공항이 정말 많네요!
누군가 데이터 입력을 많이 했어요...
하지만 공항의 좋은 점은 자주 바뀌지 않는다는 것입니다.
네, 새로운 건물이 지어지고 있긴 하지만...
그리고 오래된 것들은 폐허로 남겨지고...
하지만 이 모든 일은 시간이 지나면서 일어납니다.
일반적으로 한 공항이 폐쇄되면 더 새롭고 멋진 공항이 건설되었기 때문인 경우가 많습니다.
그리고 새로운 공항을 건설하는 데는 오랜 시간이 걸립니다.
매일 토하는 것도 아니고요.
다시 공항 목록으로 돌아가서...
길든 길지 않든, 사용자가 선택할 수 있도록 어떤 형태로든 공항 목록을 제공해야 합니다.
웹사이트가 매우 바쁜 경우 사용자가 많을 수 있습니다.
그리고 우리 모두는 웹사이트가 바쁘기를 원합니다.
이제 웹 사이트가 바쁘다고 가정해 보겠습니다...
매우 바쁩니다.
매일 수백만 명의 사용자.
매분 수천 명의 사용자가 접속합니다.
그 많은 공항 목록을 제공해야 할 때가 많습니다!
따라서 먼저 Couchbase 버킷의 공항 문서가 여행 샘플 버킷의 문서와 같은 구조로 되어 있다고 가정해 보겠습니다.
이봐요, 카우치베이스 서버 제품과 함께 제공되므로 사용하는 것이 좋습니다!
일을 쉽게...
따라서 간단한 N1QL 쿼리를 사용하여 공항을 나열하기만 하면 됩니다:
1 2 3 4 |
선택 `여행-샘플`.* FROM `여행-샘플` 어디 유형 = "공항" ; |
이렇게 하세요:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
[ { "공항명": "칼레 덩케르크", "city": "Calais", "country": "프랑스", "faa": "CQF", "geo": { "alt": 12, "lat": 50.962097, "lon": 1.954764 }, "icao": "LFAC", "id": 1254, "type": "공항", "tz": "유럽/파리" }, { "공항명": "페론 세인트 쿠엔틴", "city": "페론", "country": "프랑스", "faa": null, "geo": { "alt": 295, "lat": 49.868547, "lon": 3.029578 }, "icao": "LFAG", "id": 1255, "type": "공항", "tz": "유럽/파리" }, ... ] |
사용자가 필요한 것을 찾기가 쉽지 않을 것 같네요. FAA 공항 코드를 기준으로 정렬한 다음 코드가 0인 항목을 제거하면 어떨까요?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
[ { "공항명": "랜즈다운 공항", "city": "영스타운", "country": "미국", "faa": "04G", "geo": { "alt": 1044, "lat": 41.1304722, "lon": -80.6195833 }, "icao": null, "id": 8534, "type": "공항", "tz": "America/New_York" }, { "공항명": "모튼 필드 시립 공항", "city": "터스키기", "country": "미국", "faa": "06A", "geo": { "alt": 264, "lat": 32.4605722, "lon": -85.6800278 }, "icao": null, "id": 8317, "type": "공항", "tz": "미국/시카고" }, ... ] |
더 나은 방법이지만 웹사이트에 제공해야 하는 데이터보다 더 많은 데이터입니다.
따라서 반환되는 내용을 FAA 코드, 공항 이름, 도시 및 국가로 줄여 보겠습니다:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[ { "공항명": "랜즈다운 공항", "city": "영스타운", "country": "미국", "faa": "04G" }, { "공항명": "모튼 필드 시립 공항", "city": "터스키기", "country": "미국", "faa": "06A" }, ... ] |
자, 이제 우리가 찾고 있는 것에 대해 알아봅시다.
따라서 이를 쿼리하면 약 50~60ms의 응답 시간을 얻을 수 있습니다.
나쁘지 않네요.
하지만 이 목록에 대한 요청이 매분 수천 건씩 들어오는 상황에서...
조금 더 속도를 높일 수 있을 것 같네요.
필요한 모든 것을 포함하는 자체 인덱스를 추가하여 커버 쿼리로 만들어 보겠습니다.
1 2 |
만들기 INDEX myFaaIndex on `여행-샘플`(faa asc,공항명,도시,국가) 어디 유형 = "공항" AND faa IS NOT NULL; |
이제 쿼리를 다시 실행하여 약 17.5ms의 응답 시간을 얻었습니다.
훨씬 나아졌습니다.
하지만 이보다 더 좋은 방법이 있을까요?
이 목록은 매분 수천 번씩 요청될 것입니다.
그 밀리초가 합산됩니다.
그렇다면 이 쿼리의 결과를 하나의 문서로 저장하면 어떨까요?
"airport_list"라고 부르겠습니다.
이제 "USE KEYS" 절을 사용하여 전체 문서를 선택하는 쿼리를 실행하면 됩니다:
1 2 3 |
선택 `여행-샘플`.* FROM `여행-샘플` 사용 키 "airport_list"; |
이를 통해 약 14.5ms의 응답 시간을 제공하고 있습니다.
3밀리초를 더 절약했습니다!
키-값 액세스를 사용하고 데이터 서비스에서 직접 ID로 문서를 가져오면 0.5밀리초를 더 절약할 수 있습니다.
1분에 수천 번씩 제공해야 하는 문서에 적합합니다.
하루에도 수백만 번씩.
그 밀리초가 합산됩니다.
네, 알아요. 공항은 수시로 바뀝니다.
네, 하지만 자주 변경되지는 않습니다.
예, 이 문서는 자주 교체해야 합니다.
하지만 이는 활동이 많은 웹사이트에 서비스를 제공하지 않는 작업입니다.
따라서 그 과정이 (상대적으로) 얼마나 느려도 상관없습니다.
게다가 더 이상 커버링 인덱스가 필요하지 않습니다!
인덱스 서버의 공간을 조금 절약할 수 있습니다!
우후! 보너스!
네, 알아요 이상한 일이 생기면 흥분되죠...
지금까지는 응답 시간을 밀리초 단위로 단축하는 연습을 했습니다. 조금 더 오래 걸리고 더 많은 작업을 수행하는 쿼리는 어떨까요?
콜센터를 운영하면서 팀이 얼마나 빨리 수신 전화를 받는지 추적하는 것이 중요하다고 가정해 보겠습니다.
좀 더 구체적으로 설명해 보겠습니다.
5초, 10초 이내에 응답한 전화 수와 오늘 들어온 총 전화 수를 보여주는 대시보드가 필요하다고 가정해 보겠습니다.
예를 들면...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
선택 SUM(다섯) as fiveCount, SUM(열) as tenCount, SUM(수신) as callCount FROM (선택 사례 언제 connectTime = 0 AND (endTime - 시작 시간) <= 5000 그때 1 언제 connectTime > 0 AND (acceptTime - 시작 시간) <= 5000 그때 1 기타 0 END as 다섯, 사례 언제 connectTime = 0 AND (endTime - 시작 시간) <= 10000 그때 1 언제 connectTime > 0 AND (acceptTime - 시작 시간) <= 10000 그때 1 기타 0 END as 열, 1 as 수신 FROM sigc 어디 유형='cdr' AND 시작 시간 > $오늘 AND callType 사이 10 AND 2000) as 통화 ; |
따라서 시작 시간 및 호출 유형 속성에 대한 인덱스로 시작하여 "cdr" 유형의 문서로 제한하면 이 쿼리를 실행하는 데 약 1초가 걸린다는 것을 알 수 있습니다.
대시보드를 채우는 데 이 쿼리만 사용할 수 있는 것은 아닙니다...
당밀처럼 느리네요!
이제 모든 속성이 포함된 새 인덱스를 작성하여 이를 커버 쿼리로 만들어 보았는데, 개선되기는 했지만 여전히 약 100밀리초가 걸린다는 것을 알 수 있었습니다.
10배나 향상되었습니다! 대단하지 않나요?
대시보드만 여전히 당밀이 흐르는 것처럼 새로 고쳐집니다.
얇고 묽은 당밀이지만 여전히 ...
이 문제를 개선하려면 어떻게 해야 할까요?
이 쿼리를 사용하여 대시보드에 공급하는 대신 출력을 가져와서 결과만 있는 새 문서를 만드는 데 사용한다면 어떨까요?
call_stats_와 같이 알려진 이름이 있는 항목...
그리고 이 쿼리를 타이머로 실행하거나, 크론 작업을 사용하거나, Couchbase Eventing 서비스를 사용하여 트리거할 수 있습니다.
이벤트 서비스에서 트리거하는 경우에만 쿼리를 트리거하는 데 사용하는 문서 업데이트를 포함하도록 스캔 일관성이 최소한 at_plus로 실행하는 것이 좋습니다.
하지만 이제는 결과 문서를 검색할 때 한 자릿수 밀리초대의 낮은 응답 시간을 달성하고 있어 성능이 1000배 가까이 향상되었습니다!
이제 반응형 대시보드가 생겼습니다!
우후!!!
이제 터보 부스터 속도에 대해 이야기하고 있습니다!
그렇다면 이 두 가지 시나리오에서 얻을 수 있는 교훈은 무엇일까요?
데이터에 필요한 모든 처리를 백그라운드 작업으로 만들어 대화형 데이터 요청에 처리가 필요하지 않도록 함으로써 매우 빠르게 작업할 수 있었습니다...
과속 총알보다 더 빠른 속도로 이야기하고 있습니다!
실례합니다 슈퍼맨, 지나갈게요...
그렇다면 그 사고 실험이 정말 그렇게 고통스러웠을까요?
이제 섹시한 인덱스에 대해 알아보겠습니다...
모든 곳의 데이터 전문가에게 힘을 실어주는 Couchbase...
(피터, 새 슬로건을 찾은 것 같아요!)