지난 포스팅에 이어 작성해보고자 한다 !
시스템의 전반적인 형태를 파악했으니, 이제 그 가운데 몇 부분을 좀 더 상세히 살펴보자.
먼저 본 설계에서 가장 중요한 두 가지 테이블인 사업장 (business) 테이블과 지리 정보 색인 (geospatial index) 테이블의 규모 확장성을 살펴보겠다.
사업장 테이블 데이터는 한 서버에 담을 수 없을 수도 있다. 따라서 샤딩을 적용하기 좋은 후보다 이 테이블을 샤딩하는 가장 간단한 방법은 사업장 ID를 기준으로 하는 것이다. 모든 샤드에 부하를 고르게 분산할 수 있을 뿐 아니라 운영적 측면에서 보자면 관리하기도 쉽다.
지오해시나 쿼드트리 둘 다 널리 사용되지만 본 설계안에서는 좀 더 단순한 지오해시를 사용하도록 하겠다. 지오해시 테이블 구성 방법은 두 가지다.
방안 1
: 각각의 지오해시에 연결되는 모든 사업장 ID를 JSON 배열로 만들어 같은 열에 저장하는 방법이다. 따라서 특정한 지오해시에 속한 모든 사업장 ID가 한 열에 보관된다.
방안 2
: 같은 지오해시에 속한 사업장 ID 각각을 별도 열로 저장하는 방안이다. 따라서 사업장마다 한 개 레코드가 필요하다.
다음 표는 이 테이블에 지오해시와 사업장 ID를 저장한 사례다.
추천: 두 번째 방안을 추천하는데 그 이유는 다음과 같다.
방안 1의 경우 사업장 정보를 갱신하려면 일단 JSON 배열을 읽은 다음 갱신 할 사업장 ID를 찾아내야 한다. 새 사업장을 등록해야 하는 경우에도 같은 사업장 정보가 이미 있는지 확인을 위해 데이터를 전부 살펴야 한다. 또한 병렬적으로 실행되는 갱신 연산 결과로 데이터가 소실되는 경우를 막기 위해 락을 사용해야 한다. 따져야 할 경계 조건이 너무 많다.
하지만 방안 2의 경우에는 지오해시와 사업장 ID 칼럼을 합친 (geohash, business_id)를 복합 키(compound key)로 사용하면 사업장 정보를 추가하고 삭제하기가 쉽다. 락을 사용할 필요가 없기 때문이다.
지리 정보 색인의 규모를 확장할 때 테이블에 보관되는 데이터의 실제 크기를 고려하지 않고 성급하게 샤딩 방법을 결정하는 실수를 혼히 저지르곤 한다.
지금 살펴보는 설계안의 경우 지리 정보 색인 테이블 구축에 필요한 전체 데이터 양은 많지 않다(쿼드트리 색인 전부를 보관하는데 불과 1.71G의 메모리가 필요하며 지오해시의 경우도 비슷하다).
따라서 색인 전부를 최신 데이터베이스 서버 한 대에 충분히 수용할 수 있다. 하지만 읽기 연산의 빈도가 높다면 서비 한 대의 CPU와 네트워크 대역폭으로는 요청 전부를 감당하지 못할 수도 있다.
그런 상황에서는 여러 데이터베이스 서버로 부하를 분산해야 한다.
관계형 데이터베이스 서버의 경우 부하 분산에는 두 가지 전략이 흔히 사용 된다.
많은 엔지니어가 면접 시에 샤딩을 이야기하고 싶어 한다. 하지만 지오해시 테이블은 샤딩이 까다로우므로, 이야기하지 않는 것이 좋다. 샤딩 로직을 애플 리케이션 계층(application layer)에 구현해야 하기 때문이다. 물론 샤닝이 유일한 선택지인 경우도 있다. 하지만 이번 설계안의 경우에는 데이터 전부를 서버 한 대에 담을 수 있으므로 여러 서버로 샤딩해야 할 강한 기술적 필요성은 없다.
따라서 이번 설계안에서는 읽기 부하를 나눌 사본 데이터베이스 서버를 두는 방법이 더 좋을 것이다. 개발도 쉽고 관리도 간편하다. 이런 이유에서 지리 정보 색인 테이블의 규모를 확장할 때는 사본 데이터베이스 활용을 추천한다.
캐시 계층 도입 전에는 이런 질문을 먼저 던져야 한다. 정말 필요한가? 정말 좋은 결과로 이어지리라는 결론을 쉽게 내리기는 어려울 것이다.
면접관과 캐시 도입을 의논할 때는 벤치마킹과 미용 분석에 각별히 주의해야한다.
가장 직관적인 캐시 키는 사용자 위치의 위도 경도 정보다. 하지만 여기에는 몇가지 문제가 있다.
따라서 사용자의 위치 정보는 캐시 키로는 적절치 않다. 위치가 조금 달라지더라도 변화가 없어야 이상적이다.
지오해시나 쿼드트리는 이 문제를 효과적으로 해결한다. 같은 격자 내 모든 사업장이 같은 해시 값을 갖도록 만들 수 있기 때문이다.
아래 표 데이터는 캐시에 보관하면 시스템의 성능을 전반적으로 향상시킬 수 있다.
격자 내 사업장 ID
사업장 정보는 상대적으로 안정적이라 자주 변경되지 않는다. 따라서 특정 지오해시에 해당하는 사업장 ID 목록을 미리 계산한 다음 레디스 같은 키-값 저장소에 캐시할 수 있다. 캐시를 활용하여 주변 사업장을 검색하는 구체적 사례를 한번 살펴보자.
SELECT business_id FRoM geohash_index WHERE geohash LIKE
{:geohash}%
public List<String> getNearbyBusinessIds (String geohash) {
String cacheKey = hash(geohash);
List<String> listOfBusinessIds = Redis.get(cacheKey);
if (listOfBusinessIds == null ) {
listOfBusinessIds = 위 SQL 질의를 실행하여 구한다;
Cache.set(cacheKey, listOfBusinessIds, "1d");
}
return listOfBusinessIds;
}
새로운 사업장을 추가하거나, 기존 사업장 정보를 편집하거나, 아예 삭제하는 경우에는 데이터베이스를 갱신하고 캐시에 보관된 항목은 무효화 한다.
이 연산의 빈도는 상대적으로 낮아서 락을 사용할 필요가 없으므로, 사업장 정보 갱신은 구현하기 쉽다.
주어진 요구사항에 따르면 사용자는 다음 네 가지 검색 반경 가운데 하나를 고를 수 있다. 500m, 1km, 2km 그리고 5km. 이 검색 반경은 각각 지오해시 길이4,5,5,6에 해당한다. 이 각각에 대한 주변 사업장 검색 결과를 신속하게 제공하려면 이 세 가지 정밀도(4,5,6) 전부에 대한 검색 결과를 레디스에 캐시해 두어야 한다.
앞서 언급한 대로, 사업장 개수는 200m이고 각각의 사업장은 주어진 정밀도의 격자 하나에 대응될 것이다. 따라서 필요한 메모리 요구량은 다음과 같다.
메모리 요구량으로만 보면 서비 한 대로도 충분할 것 같다. 하지만 고가용성 (high availability)을 보장하고 대륙 경계를 넘는 트래픽의 전송지연(latency) 을 방지하기 위해서는 레디스 클러스터를 전 세계에 각 지역별로 두고, 동일한 데이터를 각 지역에 중복해서 저장해 두어야 한다. 이런 종류의 레디스 캐시를 최종 설계 도면에서는 지오해시(Geohash)라고 지칭한다.
지금까지 살펴본 위치 기반 서비스는 여러 지역과 가용성 구역에 설치한다.
면접관이 이런 추가 질문을 던질 수도 있다. 지금 영업 중인 사업장, 혹은 식당 정보만 받아오고 싶다면 어떻게 해야 하겠는가?
지원자: 지오해시나 쿼드트리 같은 메커니즘을 통해 전 세계를 작은 격자들로
분할하면 검색 결과로 얻어지는 사업장 수는 상대적으로 적습니다. 그러니 일단은 근처 사업장 ID부터 전부 확보한 다음 그 사업장 정보를 전부 추출해서 영업시간이나 사업장 유형에 따라 필터링하면 되겠죠.
물론 그러려면 영업시간이나 사업장 유형 같은 정보는 사업장 정보 테
이분에 이미 보관되어 있어야 합니다.
이 모두를 한 도면에 정리하면 다음과 같다.
list_of_geohashes = [my_geohash, neighbor1_geohash, neighbor2_
geohash, ..., neighbor8_geohash]
모든 사업장 정보 관련 API는 LBS와는 분리되어 있다. 사업장 상세 정보를 확인하기 위해 사업장 정보 서비스는 우선 해당 데이터가 '사업장 정보' 레디스 서버에 기록되어 있는지 살핀다.
캐시되어 있는 경우에는 해당 데이터를 읽어 클라이언트로 반환한다. 캐시에 없는 경우에는 데이터베이스 클러스터에서 사업장 정보를 읽어 캐시에 저장한 다음 반환한다. 뒤이은 요청은 캐시로 처리할 수 있도록 하기 위함이다.
새로 추가하거나 갱신한 정보는 다음날 반영된다는 것을 사업장과 합의하였으므로, 캐시에 보관된 정보 갱신은 밤사이 작업을 돌려서 처리할 수 있다.
이번 장에서 우리는 주변 검색 기능의 핵심인 근접성 서비스를 설계해 보았다.
지리 정보 색인 기법을 활용하는 전형적인 LBS 서비스다. 이번 장에서는 다음 몇 가지 색인 방안을 살펴보았다.
• 2차원 검색
• 균등 분할 격자
• 지오해시
• 퀴드트리
• 구글 S2