내 마지막 조각에서는 MySQL과 CouchBase Server 2.0의 기본 메커니즘을 살펴보고, 데이터를 모델링하는 방식과 기본 쿼리 및 레코드 목록이 작동하는 방식을 MySQL SQL 문과 CouchBase Server 보기에서 비교했습니다.
이번에는 이러한 뷰를 작성하는 방법과 WHERE 및 GROUP BY 절과 같은 SQL 쿼리의 특정 요소, 데이터 및 애플리케이션 로직을 실제로 Couchbase Server로 마이그레이션하는 프로세스를 살펴보겠습니다.
실행하려는 쿼리에 대해 생각해 보세요.
이미 살펴보았듯이 Couchbase Server 데이터베이스 쿼리는 실제로 2단계 프로세스로 이루어집니다. 첫 번째 단계는 데이터베이스의 문서에 대한 검색 또는 선택을 지원하는 데이터에 대한 보기를 만드는 것입니다. 두 번째 단계는 뷰에서 선택하려는 URL 및 키 값입니다. 즉, Couchbase 데이터베이스에서 정보를 가져올 방법을 생각할 때 데이터를 검색할 방법을 생각해야 하며, 이를 통해 작성해야 하는 보기의 구조를 정의할 수 있습니다.
카우치베이스 서버는 뷰를 사용하여 데이터베이스에서 문서 목록을 만들며, 뷰의 출력은 키와 해당 값입니다. 키와 값은 모두 모든 JSON 값이 될 수 있습니다. 출력의 키는 검색, 정렬 및 페이지 매김 메커니즘의 기초를 형성하기 때문에 중요합니다.
예를 들어 레시피 제목을 키로 사용하여 데이터베이스의 모든 레시피 문서만 출력하는 보기 함수는 다음과 같이 보일 수 있습니다:
if (doc.type === 'recipe' &&
doc.title !== null) {
emit(doc.title, null);
}
}
이를 맵 함수라고 하며 문서의 정보를 사용하려는 형식으로 매핑합니다. 선택적 두 번째 단계는 reduce 함수라고 하며, MySQL 집계 함수 및 GROUP BY 절과 유사합니다.
MySQL에서 마이그레이션할 때 출력하거나 쿼리하려는 정보와 일치하도록 보기를 구성하면 정보를 저장하는 방법에도 영향을 미칠 수 있습니다. 레시피 예제로 돌아가 보겠습니다. MySQL에서는 재료 테이블에 대한 쿼리를 사용하여 당근과 일치하고 일치하는 레시피 목록을 가져옵니다. 예를 들어 SQL 쿼리로 이를 다음과 같이 표현할 수 있습니다:
(ingredients.recipeid = recipe.id) 여기서 ingredient = '당근'
Couchbase에서 동일한 결과를 얻는 한 가지 방법은 다음과 같이 레시피 문서에서 각 재료에 대해 하나의 행을 출력하는 뷰를 작성하는 것입니다:
if (doc.type == 'recipe' &&
doc.title !== null) {
doc.ingredients의 id에 대해 {
emit(doc.ingredients[id].ingredient, null);
}
}
}
쿼리하려면 출력에서 일치시키려는 키를 지정해야 합니다. 쿼리는 REST API 또는 클라이언트 라이브러리를 통해 수행되지만 사양은 동일합니다. 예를 들어 '당근'을 검색하려면 찾고자 하는 키로 '당근'을 지정해야 합니다. REST API 사용:
키는 모든 JSON 값이므로 쿼리에 제공하는 키 값도 올바르게 인코딩된 JSON이어야 한다는 점을 기억하세요. 이 예에서 볼 수 있듯이 뷰는 쿼리의 기초를 제공하고 데이터베이스는 선택 메커니즘에 액세스합니다.
이러한 방식으로 뷰를 사용한다는 것은 대부분의 애플리케이션이 결국 여러 개의 동시 뷰로 구성된다는 것을 의미합니다. 보기를 구축하는 과정은 애플리케이션 내에서 핵심 쿼리를 구성한 다음 출력을 생성하는 쿼리 및 인덱스를 최적화하는 것과 크게 다르지 않습니다. 가장 큰 차이점은 뷰 정의가 애플리케이션 코드 내의 쿼리가 아니라 데이터베이스 내에 저장된다는 점입니다.
집계
MySQL에서 집계는 여러 곳에서 사용됩니다. 집계는 GROUP BY 절과 정보를 수집하거나 요약하는 함수의 조합으로 처리됩니다. 레시피 예제를 다시 사용하여 포함된 재료에 따라 다양한 레시피의 개수를 제공하는 쿼리를 실행할 수 있습니다. 예를 들어 다음과 같은 쿼리입니다:
는 재료 목록과 함께 각 재료 옆에 해당 재료가 포함된 레시피의 개수를 표시하는 카운트를 제공합니다. 최종 애플리케이션에서는 '가장 인기 있는 레시피 재료', 다른 앱에서는 '가장 인기 있는 블로그 게시물' 등으로 구성됩니다.
예를 들어, 위와 같은 결과를 얻으려면 위에서 사용한 것과 동일한 맵 함수를 사용할 수 있습니다. 그런 다음 내장된 reduce 함수 중 하나인 _count를 사용하여 emit() 함수가 출력한 고유 키의 수를 기반으로 카운트를 생성할 수 있습니다. reduce 함수가 있는 경우 뷰에 액세스할 때 자동으로 사용됩니다. 뷰에 액세스하면 개별 재료에 대한 레시피 수 목록이 생성됩니다:
{"key":"바나나","value":20},
{"key":"bananas","value":33},
{"key":"baps","value":1},
{"key":"바베큐 소스","value":1},
{"key":"바스마티 쌀","value":16},
{"key":"베이 리프","value":58},
{"key":"월계수 잎","value":35},
{"key":"콩나물","value":18},
{"key":"콩실국수","value":1},
{"key":"소고기찜스테이크","value":17}
]
}
집계와 축소 함수는 다른 작업에도 사용할 수 있습니다. 예를 들어 1부에서는 준비 및 조리 시간 정보가 있는 레시피를 보여드렸습니다. 지도와 감소 함수를 함께 사용하여 각 레시피의 총 요리 시간을 얻을 수 있습니다. 먼저 레시피 제목을 키로, 조리 시간 정보를 값으로 출력하는 지도 함수를 작성합니다:
if (doc.title) {
emit(doc.title, parseInt(doc.preptime));
emit(doc.title, parseInt(doc.cooktime));
}
}
{"key":"애플 파이","value":55},
{"key":"살구 치즈 케이크","value":70},
{"key":"바베큐 가지","value":10},
{"key":"바비큐 비프버거","value":30},
{"key":"바비큐 옥수수 옥수수 조각","value":13},
{"key":"소고기 레드와인","value":144},
{"key":"블루치즈와 토마토 딥","value":5},
{"key":"케이준치킨","value":20},
{"key":"캐리비안 코블러 스튜","value":95},
{"key":"차파티 또는 로티","value":45}
]
SQL에서는 이 정보를 쉽게 쿼리할 수 있도록 총 요리 시간을 테이블에 저장할 수 있습니다. 카우치베이스에서는 뷰를 작성할 때 정보를 처리하고 결합하는 기능을 활용할 수 있습니다.
범위 쿼리
여러 필드에서 선택하는 더 복잡한 쿼리는 적절한 보기를 만든 다음 키 쿼리 인수를 사용하여 찾고자 하는 내용을 지정하는 경우입니다. 여러 값 범위에 대한 정보를 가져오려는 경우 시작 키와 끝 키를 사용하여 뷰에서 반환할 범위를 지정할 수 있습니다. 예를 들어 요리 시간을 키로 사용하여 뷰를 구성하면 요리 시간이 5분에서 25분 사이인 모든 일치하는 레시피 목록을 출력할 수 있습니다:
이는 다음과 유사합니다:
여러 필드에서 쿼리하기
여러 필드에 대해 쿼리하려면 쿼리하려는 두 개 이상의 필드를 출력하는 보기를 구성해야 합니다. 예를 들어, 요리법을 찾을 때 특정 재료가 포함된 요리법을 찾고 싶을 뿐만 아니라 특정 시간 제한 내에 있는 요리법을 찾고자 하는 경우가 매우 흔합니다. 외출 30분 전에 냉장고를 열어 토마토를 발견했다면 20분 안에 토마토로 어떤 요리를 할 수 있는지 알고 싶을 것입니다. 이를 위해 다음과 같이 재료와 레시피에 필요한 시간이 포함된 키를 출력하는 뷰를 출력합니다:
만약 (doc.재료) {
에 대한 (i=0; i < doc.재료.길이; i++) { 만약 (doc.재료[i].성분 != null)
{
emit([doc.재료[i].성분, parseInt(doc.총 시간)], null);
}
}
}
}
{“id”:”4997AFDE-3027-11E2-BB0D-B67A7E241592″,”key”:[“tomatoes”,5],”value”:null},
{“id”:”79C16D8A-3027-11E2-BB0D-B67A7E241592″,”key”:[“tomatoes”,5],”value”:null},
{“id”:”CF52D23E-3027-11E2-BB0D-B67A7E241592″,”key”:[“tomatoes”,5],”value”:null},
{“id”:”42F5D520-3027-11E2-BB0D-B67A7E241592″,”key”:[“tomatoes”,6],”value”:null},
{“id”:”1A0AB61C-3027-11E2-BB0D-B67A7E241592″,”key”:[“tomatoes”,8],”value”:null},
{“id”:”4D0F9DF2-3027-11E2-BB0D-B67A7E241592″,”key”:[“tomatoes”,8],”value”:null},
{“id”:”50CE1676-3027-11E2-BB0D-B67A7E241592″,”key”:[“tomatoes”,10],”value”:null},
{“id”:”564A66CC-3027-11E2-BB0D-B67A7E241592″,”key”:[“tomatoes”,10],”value”:null},
{“id”:”95E9464A-3027-11E2-BB0D-B67A7E241592″,”key”:[“tomatoes”,10],”value”:null},
{“id”:”9A78477E-3027-11E2-BB0D-B67A7E241592″,”key”:[“tomatoes”,10],”value”:null}
]
}
멋지네요! SQL 문과 비교합니다:
참고로, 문서와 레시피를 로드할 수 있도록 각 문서의 ID를 가져옵니다. 출력물에 레시피 제목이 없을 수도 있지만 클라이언트 라이브러리가 자동으로 문서 정보를 로드할 수 있습니다. 어려운 부분은 검색을 수행하는 것입니다.
보기 출력 정렬
뷰 출력의 정렬은 자동으로 이루어집니다. 모든 뷰의 출력은 각 뷰에서 생성된 키에 따라 자동으로 정렬됩니다. 이 기능이 없으면 범위 쿼리를 수행할 수 없으므로 이 기능이 필요합니다. 이를 통해 일부 작업을 빠르고 쉽게 수행할 수 있습니다. 레시피 목록을 제목의 알파벳 순서로 출력하려면 레시피 제목을 키로 출력하는 보기를 만들면 됩니다. 즉, 맵 기능을 사용합니다:
{
emit(doc.title, null);
}
와 유사합니다:
MySQL에서 내림차순으로 목록을 가져오려면 DESC 키워드를 사용합니다:
카우치베이스 서버에서는 URL에 내림차순 매개변수를 사용합니다:
페이지 매김
예를 들어 레시피 제목 쿼리에서 처음 10개의 레코드를 가져오는 것과 같이 MySQL에서 페이징은 LIMIT 및 OFFSET 절의 조합을 사용하여 처리됩니다:
다음 10개의 레코드를 가져옵니다:
카우치베이스 서버에서는 뷰 쿼리에 제한 및 건너뛰기 인수를 제공하여 비슷한 설정을 사용합니다. 처음 10개의 레코드를 가져옵니다:
그리고 다음 10개의 기록:
비교적 짧은 페이지 블록을 다룰 때는 이 방법이 잘 작동합니다. 페이지가 길거나 많은 수의 출력 행을 건너뛸 가능성이 있는 경우 startkey_docid를 사용하여 뷰 내에서 앞으로 건너뛸 수 있습니다. 이 방법은 이러한 긴 페이지 매김 시나리오에 더 효율적입니다. 뷰의 문서 에서 자세한 내용을 확인하세요.
핵심 개체 로드
MySQL의 레시피 데이터베이스 예시에서는 단일 레시피에 대한 데이터베이스 정보를 로드하여 표시하는 데 필요한 여러 테이블이 있습니다. Couchbase Server 내에서 특정 레시피에 대한 문서 ID가 있으면 Couchbase Server 데이터베이스에 저장된 전체 레시피 레코드 구조에 액세스할 수 있습니다.
MySQL을 사용하면 이러한 복잡한 객체의 로드 메커니즘을 단순화할 필요가 있기 때문에 로드된 복잡한 데이터를 캐시하여 더 쉽게 액세스할 수 있도록 하는 여러 가지 지원 프로젝트가 생겨났습니다(예: 멤캐시). 카우치베이스 서버는 데이터베이스에서 정보를 로드할 필요성을 없애지는 않지만, 레시피 레코드를 로드하는 작업을 여러 번 쿼리하고 해당 데이터를 디스플레이에 사용되는 구조로 포맷하는 대신 한 번의 작업으로 수행할 수 있게 해줍니다.
예를 들어 SQL에서는 개별 테이블을 보고 개체를 구성하고 여러 가지 쿼리를 실행하여 레시피 데이터를 로드할 수 있습니다:
SELECT * FROM ingredients WHERE recipeid=23434
SELECT * FROM methods WHERE recipeid=23434
SELECT * FROM keywords WHERE recipeid=23434
그 결과 레시피 데이터가 JSON으로 저장됩니다:
{
"title" : "아로마 로스트 치킨",
"PREPTIME" : "15",
"servings" : "6",
"총시간" : "120",
"자막" : "쿠스쿠스 속과 향긋한 라임과 구운 마늘이 들어간 향긋한 향신료가 단순한 로스트 치킨을 특별한 것으로 바꿔줍니다.",
"cooktime" : "105",
"keywords" : [
"diet@dairy-free",
"cook method.hob, oven, grill@oven",
"다이어트@피넛프리",
"스페셜 컬렉션@체피 추천",
"다이어트@콘프리",
"스페셜 컬렉션@주말 식사",
"occasion@entertaining",
"스페셜 컬렉션@매우 쉬운",
"다이어트@이스트프리",
"다이어트@조개류 없음",
"스페셜 컬렉션@필링 스파이시!",
"주재료@가금류",
"다이어트@데미채식",
"식사 유형@메인",
"다이어트@에그프리",
"다이어트@우유 무첨가"
],
"ingredients" : [
…
],
"method" : [
…
],
}
한 번의 쿼리, 데이터베이스에 대한 한 번의 요청으로 레시피를 한 페이지에 표시하는 데 필요한 모든 것을 로드할 수 있습니다. 사용자 댓글, 추가 메타데이터 등 다른 콘텐츠도 물론 여기에 포함될 수 있으며, 모두 단일 문서에 저장되어 단 한 번의 요청으로 처리할 수 있습니다.
데이터 마이그레이션
마이그레이션을 시작할 때 데이터 형식을 고려할 때 또 다른 중요한 점이 떠올랐습니다. 데이터와 데이터가 어떻게 사용될지 생각해 보세요. MySQL에서 여러 소스에서 가져온 정보를 표시하는 경우, 이 정보를 함께 그룹화하여 Couchbase Sever에 단일 문서로 저장하는 것이 좋습니다.
이미 언급했듯이 MySQL에 사용되는 많은 가속 메커니즘은 복잡한 구조와 정보의 로딩 속도를 높이기 위해 여러 소스에서 정보를 로드한 후 캐싱하는 방식으로 작동합니다.
위에서 언급한 구조에 대한 주의 사항을 염두에 두고 Couchbase Server에서도 동일한 기본 원칙이 적용됩니다. 데이터베이스에서 문서를 가져오는 방식이 데이터인 경우 생성한 구조가 콘텐츠를 검색할 수 있는 보기를 구성할 수 있는지 확인해야 합니다.
예를 들어, 레시피 구조에서 재료를 단일 텍스트 필드로 저장하면 콘텐츠의 세부 정보가 손실되어 의미가 없습니다. 이는 MySQL 내에서 모든 것을 하나의 텍스트 열에 넣는 것과 비슷하며 동일한 함정이 있습니다. 즉, 내용을 검색하고 쿼리하기 어렵고 보기와 표시 가능성을 모두 복잡하게 만든다는 것입니다.
실제 마이그레이션 프로세스는 매우 간단합니다. MySQL에서 정보를 로드하고 데이터베이스의 각 주요 개체에 대한 문서를 구성한 다음, 그 문서를 Couchbase Server에 작성하면 됩니다. 실제로 이 방법을 사용하여 실행 중인 애플리케이션의 정보를 마이그레이션할 수도 있습니다. 대신 Couchbase에서 항목을 로드하고, 존재하지 않는 경우 MySQL에서 로드한 다음 구성된 객체를 Couchbase에 저장하세요.
Couchbase의 문서는 ID로 저장됩니다. ID는 원하는 문자열이 될 수 있으므로 항상 뷰에 의존하여 찾을 필요 없이 사용자나 애플리케이션을 식별할 수 있는 것을 사용할 수 있습니다. 항상 존재하는 레시피 데이터베이스를 사용하려면 '생선 스튜'와 같은 레시피 제목을 사용할 수 있습니다. 이 접근 방식의 한계는 (MySQL 및 고유 인덱스와 마찬가지로) 동일한 ID를 가진 두 개의 문서를 저장할 수 없고 다양한 생선 스튜 레시피가 존재한다는 점입니다. 대안으로 UUID를 사용하거나 레시피 제목에 접미사를 사용하여 여러 개의 '생선 스튜' 레시피를 허용할 수 있습니다.
MySQL AUTO_INCREMENT 필드 옵션과 마찬가지로 Couchbase는 각 문서에 대해 UUID를 자동으로 생성합니다. 이는 강제 사항이 아니며, 특정 문서 ID와 자동 생성된 ID를 모두 동일한 데이터베이스에 저장할 수 있으므로 특정 정보를 저장할 때는 알려진 ID를 사용하고 데이터 본문에 대해서는 자동 생성된 ID를 사용할 수 있습니다.
마무리
Couchbase와 MySQL의 가장 큰 차이점은 데이터 표현과 데이터 액세스 방식입니다. 특정 데이터 모델의 경우, 카우치베이스는 이 글에서 살펴본 단일 객체 스타일의 데이터를 로드하는 문제에 대한 더 간단한 솔루션을 제공합니다. 레시피는 데이터 저장소의 핵심 요소가 레시피 전체(우리 문서)인 좋은 예입니다. 레시피 데이터를 검색하고 찾아서 목록으로 표시하는 기능은 데이터 저장 방식과 관련된 요구 사항이 아니라 데이터를 표시하고 액세스해야 하는 방식에 대한 요구 사항입니다.
이 두 개의 포스팅을 통해 MySQL에서 Couchase Server로 데이터를 이동하는 방법과 Couchbase Server 데이터 및 애플리케이션 코드를 모델링하고 개발하여 기존 MySQL 기반 인프라처럼 시스템이 작동하고 운영되도록 하는 방법에 대한 아이디어를 얻으셨기를 바랍니다.
#next_pages_container { width: 5px; height: 5px; position: absolute; top: -100px; 왼쪽: -100px; z-index: 2147483647 !important; }
#next_pages_container { width: 5px; height: 5px; position: absolute; top: -100px; 왼쪽: -100px; z-index: 2147483647 !important; }
#next_pages_container { width: 5px; height: 5px; position: absolute; top: -100px; 왼쪽: -100px; z-index: 2147483647 !important; }
#next_pages_container { width: 5px; height: 5px; position: absolute; top: -100px; 왼쪽: -100px; z-index: 2147483647 !important; }
#next_pages_container { width: 5px; height: 5px; position: absolute; top: -100px; 왼쪽: -100px; z-index: 2147483647 !important; }
#next_pages_container { width: 5px; height: 5px; position: absolute; top: -100px; 왼쪽: -100px; z-index: 2147483647 !important; }
#next_pages_container { width: 5px; height: 5px; position: absolute; top: -100px; 왼쪽: -100px; z-index: 2147483647 !important; }
이것은 훌륭한 기사입니다. 그렇다면 매핑이 얼마나 간단한지 고려할 때 왜 Couchbase 위에 SQL 또는 "SQL 같은" 쿼리 언어를 제공할 수 없나요? 내 말은 Sun은 지속성 공급자 위에 JPQL을 사용하여 이를 수행할 수 있었습니다 :-)
안녕하세요 Frank,
오늘 소개하는 쿼리 API는 뷰이며, 쿼리를 만들고, 몇 가지 기준을 설정하고, 결과를 정렬하는 매우 간단한 메서드 세트를 노출합니다. 이것은 내부적으로 REST API를 기반으로 합니다. 이 블로그 게시물에서 볼 수 있는 것이 바로 이것입니다. 이 API는 Couchbase의 맵리듀스 함수를 사용하여 생성된 인덱싱된 뷰 개념을 활용합니다. 또한 Couchbase 쿼리는 모든 언어에서 거의 동일하다는 점에 유의하는 것이 중요합니다.
두 번째 요점에 대해 JPQL에 대해 말씀하셨는데, 이는 직접적으로 Sun이 아니라 JCP, 즉 전체 Java 업계에 해당하는 것으로, Couchbase는 NoSQL 데이터베이스용 쿼리 언어를 중심으로 표준화 작업을 진행했습니다: http://www.unqlspec.org/
Couchbase R&D 팀은 더 많은 쿼리 옵션과 향후 릴리스에 대한 API를 제공하기 위해 적극적으로 노력하고 있습니다.
이 시리즈는 데이터를 읽는 데 유용했지만, 데이터 쓰기에 관한 글도 한두 번쯤은 읽어보시는 건 어떨까요? 예를 들어, 문서에서 배열의 단일 요소를 업데이트하는 것입니다.
Steve,
현재로서는 설정 또는 삭제 작업을 사용하여 Couchbase에 대한 쓰기 API가 매우 간단합니다. 속성 값을 업데이트하려면 문서를 설정해야 합니다. 하위 문서 수준 업데이트는 향후 지원하고자 하는 기능입니다. 또한, 데이터 쿼리와 데이터 조작을 더 쉽게 할 수 있는 쿼리 언어도 연구하고 있습니다. 아마도 데이터를 쿼리한 다음 데이터를 조작할 수 있게 될 것입니다.
지원해야 할 필수 조항에 대한 제안이 있으시면 알려주세요. 여러분의 의견을 듣고 싶습니다.
정말 큰 도움이 되었습니다 :-) 고마워요!