지난 포스팅에 이어 작성해보고자 한다 !

3단계 상세 설계

시스템의 전반적인 형태를 파악했으니, 이제 그 가운데 몇 부분을 좀 더 상세히 살펴보자.

  • 데이터베이스 규모 확장
  • 캐시
  • 지역 및 가용성 구역
  • 시간대 또는 사업장 유형에 따른 검색
  • 최종 아키텍처 다이어그램

데이터베이스의 규모 확장성

먼저 본 설계에서 가장 중요한 두 가지 테이블인 사업장 (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)에 구현해야 하기 때문이다. 물론 샤닝이 유일한 선택지인 경우도 있다. 하지만 이번 설계안의 경우에는 데이터 전부를 서버 한 대에 담을 수 있으므로 여러 서버로 샤딩해야 할 강한 기술적 필요성은 없다.

따라서 이번 설계안에서는 읽기 부하를 나눌 사본 데이터베이스 서버를 두는 방법이 더 좋을 것이다. 개발도 쉽고 관리도 간편하다. 이런 이유에서 지리 정보 색인 테이블의 규모를 확장할 때는 사본 데이터베이스 활용을 추천한다.

캐시

캐시 계층 도입 전에는 이런 질문을 먼저 던져야 한다. 정말 필요한가? 정말 좋은 결과로 이어지리라는 결론을 쉽게 내리기는 어려울 것이다.

  • 처리 부하가 읽기 중심이고 데이터베이스 크기는 상대적으로 작아서 모든 데이터는 한 대 데이터베이스 서버에 수용 가능하다. 이 경우 질의문 처리 성능은 I/O에 좌우되지 않으므로 메모리 캐시를 사용할 때와 비슷하다.
  • 읽기 성능이 병목이라면 사본 데이터베이스를 증설해서 읽기 대역폭을 늘릴 수 있다.

면접관과 캐시 도입을 의논할 때는 벤치마킹과 미용 분석에 각별히 주의해야한다.

캐시 키

가장 직관적인 캐시 키는 사용자 위치의 위도 경도 정보다. 하지만 여기에는 몇가지 문제가 있다.

  • 사용자의 전화기에서 반환되는 위치 정보는 추정치일 뿐 아주 정확하진 않다. 설사 전혀 움직이지 않는다고 해도, 그 정보는 측정할 때마다 조금씩 달라질 것이다.
  • 사용자가 이동하면 해당 위도 및 경도 정보도 미세하게 변경된다. 대부분의 애플리케이션에 이 변화는 아무런 의미가 없다.

따라서 사용자의 위치 정보는 캐시 키로는 적절치 않다. 위치가 조금 달라지더라도 변화가 없어야 이상적이다.
지오해시나 쿼드트리는 이 문제를 효과적으로 해결한다. 같은 격자 내 모든 사업장이 같은 해시 값을 갖도록 만들 수 있기 때문이다.

캐시 데이터 유형

아래 표 데이터는 캐시에 보관하면 시스템의 성능을 전반적으로 향상시킬 수 있다.

격자 내 사업장 ID

사업장 정보는 상대적으로 안정적이라 자주 변경되지 않는다. 따라서 특정 지오해시에 해당하는 사업장 ID 목록을 미리 계산한 다음 레디스 같은 키-값 저장소에 캐시할 수 있다. 캐시를 활용하여 주변 사업장을 검색하는 구체적 사례를 한번 살펴보자.

  1. 주어진 지오해시에 대응되는 사업장 목록은 다음 질의를 통해 구할 수 있다.
	SELECT business_id FRoM geohash_index WHERE geohash LIKE
{:geohash}%
  1. 주어진 지오해시에 대응되는 사업장 목록을 요청 받으면 일단 캐시를 먼저 조회한다. 캐시에 없는 경우에는 위의 질의를 사용하여 사업장 목록을 데이터베이스에서 가져온 다음 캐시에 보관한다.
	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이고 각각의 사업장은 주어진 정밀도의 격자 하나에 대응될 것이다. 따라서 필요한 메모리 요구량은 다음과 같다.

  • 레디스 저장소에 값(value)을 저장하기 위한 필요 공간: 8바이트 X 200m x
    3가지 정밀도 =~ 5GB
  • 레디스 저장소에 키(key)를 저장하기 위한 필요 공간: 무시할 만한 수준
  • 따라서 전체 메모리 요구량은 대략 5GB

메모리 요구량으로만 보면 서비 한 대로도 충분할 것 같다. 하지만 고가용성 (high availability)을 보장하고 대륙 경계를 넘는 트래픽의 전송지연(latency) 을 방지하기 위해서는 레디스 클러스터를 전 세계에 각 지역별로 두고, 동일한 데이터를 각 지역에 중복해서 저장해 두어야 한다. 이런 종류의 레디스 캐시를 최종 설계 도면에서는 지오해시(Geohash)라고 지칭한다.

지역 및 가용성 구역

지금까지 살펴본 위치 기반 서비스는 여러 지역과 가용성 구역에 설치한다.

  • 사용자와 시스템 사이의 물리적 거리를 최소한으로 줄일 수 있다. 미국 서부(US West) 사용자는 해당 지역 데이터센터로 연결될 것이고, 유럽 사용자는 유럽 데이터센터로 연결될 것이다.
  • 트래픽을 인구에 따라 고르게 분산하는 유연성을 확보할 수 있다. 일본과 한국 같은 지역은 인구 밀도가 아주 높나. 그런 국가는 별도 지역으로 빼거 나, 아예 한 지역 안에서도 여러 가용성 구역을 활용하여 부하를 분산시키는 것이 바람직할 수 있다.
  • 그 지역의 사생활 보호법(privacy law)에 맞는 운영이 가능하다. 어떤 국가는 사용자 데이터를 해당 국가 이외의 지역으로 전송하지 못하도록 한다. 그런 경우에는 해당 국가를 별도 지역으로 빼고, 해당 국가에서 발생하는 모든 트래픽은 DNS 라우팅을 통해 해당 지역 내 서비스가 처리하도록 해야 한다.

추가 질문: 시간대, 혹은 사업장 유형별 검색

면접관이 이런 추가 질문을 던질 수도 있다. 지금 영업 중인 사업장, 혹은 식당 정보만 받아오고 싶다면 어떻게 해야 하겠는가?

지원자: 지오해시나 쿼드트리 같은 메커니즘을 통해 전 세계를 작은 격자들로
분할하면 검색 결과로 얻어지는 사업장 수는 상대적으로 적습니다. 그러니 일단은 근처 사업장 ID부터 전부 확보한 다음 그 사업장 정보를 전부 추출해서 영업시간이나 사업장 유형에 따라 필터링하면 되겠죠.
물론 그러려면 영업시간이나 사업장 유형 같은 정보는 사업장 정보 테
이분에 이미 보관되어 있어야 합니다.

최종 설계도

이 모두를 한 도면에 정리하면 다음과 같다.

주변 사업장 검색

  1. 엘프에서 주변 반경 500미터 내 모든 식당을 찾는 경우를 생각해 보자. 우선 클라이언트 앱은 사용자의 위치(위도와 경도 정보)와 검색 반경(500미터)을 로드밸린서로 전송한다.
  2. 로드밸런서는 해당 요청을 LBS로 보낸다.
  3. 주어진 사용자 위치와 반경 정보에 의거하여, LBS는 검색 요건을 만족할 지오해시 길이를 계산한다. 이전 표에 따르면 500미터 정밀도의 지오해시 길이는 6이다.
  4. LBS는 인접한 지오해시를 계산한 다음 목록에 추가한다. 결과는 아래와 같을 것이다.
list_of_geohashes = [my_geohash, neighbor1_geohash, neighbor2_
geohash, ..., neighbor8_geohash]
  1. List_of_geohashes 내에 있는 지오해시 각각에 대해 LBS는 '지오해시' 레디스 서버를 호출하여 해당 지오해시에 대응하는 모든 사업장 ID를 추출한다. 지오해시별로 사업장 ID 목록을 가져오는 연산을 병렬적으로 수행하면 검색 결과를 내는 지연시간을 줄일 수 있다.
  2. 반환된 사업장 ID들을 가지고 '사업장 정보' 레디스 서버를 조회하여 각 사업장의 상세 정보를 취득한다. 해당 상세 정보에 의거하여 사업장과 사용자 간 거리를 확실하게 계산하고, 우선순위를 매긴 다음 클라이언트 앱에 반환한다.

사업장 정보 조회, 갱신, 추가 그리고 삭제

모든 사업장 정보 관련 API는 LBS와는 분리되어 있다. 사업장 상세 정보를 확인하기 위해 사업장 정보 서비스는 우선 해당 데이터가 '사업장 정보' 레디스 서버에 기록되어 있는지 살핀다.

캐시되어 있는 경우에는 해당 데이터를 읽어 클라이언트로 반환한다. 캐시에 없는 경우에는 데이터베이스 클러스터에서 사업장 정보를 읽어 캐시에 저장한 다음 반환한다. 뒤이은 요청은 캐시로 처리할 수 있도록 하기 위함이다.

새로 추가하거나 갱신한 정보는 다음날 반영된다는 것을 사업장과 합의하였으므로, 캐시에 보관된 정보 갱신은 밤사이 작업을 돌려서 처리할 수 있다.

4단계 마무리

이번 장에서 우리는 주변 검색 기능의 핵심인 근접성 서비스를 설계해 보았다.
지리 정보 색인 기법을 활용하는 전형적인 LBS 서비스다. 이번 장에서는 다음 몇 가지 색인 방안을 살펴보았다.

• 2차원 검색
• 균등 분할 격자
• 지오해시
• 퀴드트리
• 구글 S2

profile
코딩 해라 스리스리 예스리 얍!

0개의 댓글