"카우치디비"의 두 판 사이의 차이
kimminsung5 (토론 | 기여) |
(→주요 특징) |
||
38번째 줄: | 38번째 줄: | ||
*REST API = URI를 기준으로 POST, GET, PUT, DELETE를 이용하여 CRUD 처리가 가능하다. | *REST API = URI를 기준으로 POST, GET, PUT, DELETE를 이용하여 CRUD 처리가 가능하다. | ||
*Eventual Consistency = 분산형 컴퓨팅에서 사용되는 동시성 일관성 모델인 Eventual Consistency 보장 | *Eventual Consistency = 분산형 컴퓨팅에서 사용되는 동시성 일관성 모델인 Eventual Consistency 보장 | ||
− | *Built for Offline = 스마트폰과 같은 기기에도 데이터 복제가 허용되며, 온라인 상황에서 데이터 동기화 실시<ref>바라매, 〈[https://m.blog.naver.com/PostView.nhn?blogId=windfalcon1&logNo=220401315081&proxyReferer=https%3A%2F%2Fwww.google.com%2F CouchDB]〉, | + | *Built for Offline = 스마트폰과 같은 기기에도 데이터 복제가 허용되며, 온라인 상황에서 데이터 동기화 실시<ref>바라매, 〈[https://m.blog.naver.com/PostView.nhn?blogId=windfalcon1&logNo=220401315081&proxyReferer=https%3A%2F%2Fwww.google.com%2F CouchDB]〉, 《네이버 블로그》, 2015-06-25</ref> |
== 다른 데이터베이스(DB)와의 차이점 == | == 다른 데이터베이스(DB)와의 차이점 == |
2019년 6월 27일 (목) 11:19 판
카우치디비(Couch DB)는 Cluster Of Unreliable Commodity Hardware의 약어로 2005년에 개발이 시작되고, 2008년초에 아파치 인큐베이팅 프로젝트에 등록된 문서 기반 데이터베이스이다. 이 프로젝트를 이끌고 있는 사람은 Damien Katz씨로 로터스에서 근무했고 현재는 IBM에서 일하고 있다. 카우치디비는 아파치 프로젝트 중에서 유일하게 얼랭(Erlang)을 언어로 사용하고 있다. 얼랭으로 구현되어 있지만 사용자들은 얼랭을 알 필요가 없다.[1]
목차
개요
얼랭으로 구현된 문서 기반 분산 데이터베이스, 카우치디비(CouchDB)
카우치디비(CouchDB)는 Cluster Of Unreliable Commodity Hardware의 약어로, 2008년 2월에 아파치 인큐베이팅 프로젝트에 편입된 문서 기반 데이터베이스이다. 현재 아파치 프로젝트 중에서는 유일하게 얼랭을 언어로 사용한다. 카우치디비(CouchDB)가 얼랭으로 구현되긴 했지만 다른 [[데이터베이스]를 사용할 때 C/C++를 쓰는 게 아니라 SQL을 쓰는 것처럼, 카우치디비(CouchDB)를 사용하기 위해 얼랭을 알아야 할 필요는 없다. 하지만, 관계형 데이터베이스가 아닌 문서 기반 데이터베이스이기 때문에, SQL 대신 JSON과 자바스크립트를 사용한다는 것이 대단히 특이한 점이라 할 수 있다. JSON은 데이터 표현에 사용되고, 자바스크립트는 쿼리에 사용된다. 대부분의 플랫폼과 언어들이 [[HTTP]를 지원하고, JSON 파싱도 어려운 일이 아니므로 어떤 환경에서도 쉽게 카우치디비(CouchDB)와 인터페이스 할 수 있다. 이런 면에서는 예전에 루비 세미나에서 deepblue님이 발표하신 슬러거와 비슷한 점이 있다. 그러나 RSET 스타일의 JSON 프로토콜을 사용한다는 것 이외에도 복제와 MapReduce 프로그래밍 모델이 자연스럽게 지원된다는 것이 가장 큰 특징이다. 이것이 데이터베이스에서 지원된다면 또 다른 가능성이 열리는 셈이다. 양방향 복제도 지원되고, 오프라인 모드로 사용하다가 나중에 동기화하는 것 역시 가능하다.[2]
얼랭(Erlang)이란?
얼랭(Erlang)은 범용 병렬 프로그래밍 언어이다. 함수형 언어가 효율적으로 산업 현장 등에서 사용되는 유명한 사례이다. 원래는 에릭슨(Ercisson)사에서 스위칭 소프트웨어에서 사용하기 위해 계발했지만, 1998년에 오픈 소스로 공개되었다. 흔히 Erlang이라는 이름이 Ericsson Language에서 따온 것이라고 생각하지만, 통신이론을 연구한 덴마크의 수학자 Agner Krarup Erlang의 이름에서 따온 것이다.[3]
기술 개요
- 문서 저장소
문서는 카우치디비(CouchDB)에서 가장 기본적인 데이터 단위이며, 다수의 필드와 첨부 파일로 구성된다. 문서에는 데이터베이스 시스템이 관리하는데 필요한 메타데이터도 포함된다. 각 문서 필드는 유일한 이름을 가지고 있고, 텍스트, 숫자, 불린, 리스트 등 다양한 타입의 값을 사용할 수 있으며, 텍스트 크기나 필드 수에는 아무런 제약이 없다. CVS나 서브버전을 쓴다면 익숙하겠지만, 카우치디비(CouchDB)의 업데이트 모델은 낙관적인 모델이라 잠금을 사용하지 않는다. 만약 여러 명의 사용자가 동시에 같은 문서를 편집하는 상황이 발생하면, 나중에 저장하는 사람은 충돌이 발생했음을 알게 된다. 나중에 넣는 최신 버전을 기반으로 다시 써서 넣게 되어있다.
- 모든 문서는 데이터와 연관된 인덱스 업데이트는 동기적으로 디스크에 쓴다.
- 이어서 업데이트된 데이터베이스 헤더를 쓰고, 각 청크들이 모여서 4K가 채워지면 디스크에 동기적으로 쓴다.
만약 1단계에서 운영체제 충돌이 발생하거나 전원 문제가 발생한다면, 해당 업데이트된 부분은 그냥 재시작할 때 잊힌다. 2단계에서 문제가 발생한다면, 이전의 동일한 헤더가 남아있으므로 이전에 커밋된 데이터가 일관성을 보장할 수 있다. 헤더 영역을 제외하고는 무결성 검사를 수행하거나 복구하는 작업이 전혀 필요 없다. 한편으로, 수정이나 삭제가 발생해도 계속 파일 끝에 추가만 하므로 낭비되는 공안이 상당히 발생한다. 데이터베이스 정리를 하려면 Complication이 필요한데, 유효한 데이터만 모아서 복사본을 새로 쓰고, 다 쓰면 예전 파일을 버리는 방식으로 구현돼 있다. 적당한 시점에 데이터베이스 자체적으로 이 작업을 수행하긴 하지만 관리자가 직접 명령을 내릴 수도 있다.
- 뷰
카우치디비(CouchDB)는 비정형 데이터를 다루기 위해 자바스크립트 함수를 이용해 데이터 가공을 처리한다. 이 자바스크립트 함수를 뷰라고 하는데 MapReduce 모델로 되어있다. 뷰는 카우치디비(CouchDB) 문서를 인수로 하여 계산을 수행하면서 키-값 쌍을 추가한다. Reduce 함수가 정의되어 있다면 Map 함수에서 발생시킨 키-값 쌍을 받아서 키를 기준으로 데이터를 가공한다. 조회 결과는 Map 함수에서 넘겨준 키를 기준으로 정렬된다. 키 값이 null이면 문서 ID를 기준으로 정렬한다.[4] 이렇게 처리하면 비정형 데이터라도 유연하게 처리할 수 있긴 하지만, 대용량 데이터를 처리하려면 인덱스가 필요하지 않나 생각할 것이다. 카우치디비(CouchDB)는 뷰 인덱스를 지원한다. 뷰와 함수는 설계 문서 안에 저장되고, 설계 문서는 여러 개의 뷰 함수를 포함할 수 있게 되어있다. 같은 설계 문서에 포함된 뷰는 같은 그룹으로 인덱스된다. 인덱스는 문서 ID와 뷰 함수의 결괏값으로 구성된다. 뷰 빌더는 시퀀스 ID를 이용하여 뷰 그룹이 최신 버전인지 확인하고, 만약에 최신 버전이 아닌 경우에는 마지막 변경을 반영했던 시점 이후의 모든 문서를 추적한다. 이렇게 인덱스 갱신 작업이 일어나는 도중에도 읽기 문서를 모두 찾아서 반영하고 난 이후에는 뷰 인덱스에 있던 이전 값은 버려진다. 특정 문서가 뷰 함수에 의해 선택된 경우에는 그 함수의 결괏값이 인덱스에 반영된다. 인덱스 또한 파일 끝에 계속 추가되므로, 디스크 헤드를 최적으로 움직일 수 있으며 충돌이 발생하거나 전원이 나가는 경우에도 인덱스가 깨지는 일은 없다. 인덱스를 업데이트하는 도중에 충돌이 발생한다면 불완전한 인덱스 업데이트는 그냥 버려지고 마지막으로 커밋됐던 상태로부터 다시 인덱스가 만들어진다.[2]
- 보안과 유효성 검증 = 카우치디비(CouchDB)는 누가 문서를 읽고 수정할 수 있는지 제한할 수 있도록, 간단하면서도 확장 가능한 보안 모델을 가지고 있다.
- 관리자 접근 = 관리자 계정의 경우 다른 관리자 계정을 만들거나 설계 문서를 수정할 수 있다.
- 리더 접근 = 카우치디비(CouchDB) 문서에 리더(Reader) 목록을 저장한다. 따로 정의된 게 없으면 누구나 읽을 수 있지만, 목록이 정의되어 있는 경우에는 지정된 사용자만 읽기가 가능하다. 뷰에서 접근할 때도 이 접근 제한이 동일하게 적용된다. 접근 권한이 없으면 뷰 조회 결과에서 빠지도록 되어 있다.
- 업데이트 접근 = 디스크에 문서를 쓰는 시점에 자바스크립트 함수를 이용하여 유혀성 검증이 이뤄진다. 유효성 검증을 통과하면 그대로 업데이트를 진행할 수 있는 반면, 그렇지 않은 경우에는 작업이 중단되고 클리이언트는 오류 응답을 받는다. 자바스크립트 함수의 매개변수로 사용자 계정 정보와 업데이트 한 문서가 같이 넘어오므로, 이 값을 이용하여 권한을 결정한다. 가령, 최초 작성자만 문서를 수정할 수 있도록 하고 싶다면 간단히 문서의 author 필드와 현재 사용자 계정을 비교하도록 코드를 작성하면 된다.[4]
- 분산 업데이트와 복제 = 카우치디비(CouchDB)는 P2P 기반의 분산 데이터베이스 시스템이기 때문에, 접속이 끊어진 상태에서도 공유 데이터를 접근하거나 수정할 수 있고, 양방향으로 변경 사항을 복제하는 것도 가능하다. 카우치디비(CouchDB)는 마지막 복제가 일어났던 시점 이후에 변경된 문서만 복제한다. 도중에 복제가 실패하더라도 그냥 마지막으로 복제 전송하던 문서부터 다시 시작하면 된다. 특히 모든 변경이 파일 끝에 추가되는 형태로 이뤄지므로 복제에 필요한 디스크 헤드의 움직임도 효율적이다. 특정 기준을 만족하는 문서만 복제하도록 자바스크립트 함수로 필터링해서 일부분만 복제하는 것도 가능하다. 보통 오프라인 모드로 동작해야 할 때 전체 데이터베이스를 복제하기엔 너무 시간이 오래걸리므로, 실제 사용할 부분만 복제하도록 하는 용도로 쓴다. 분산 데이터베이스 시스템에서는 충돌을 탐지하고 관리하는 것도 중요한 문제이다. 카우치디비(CouchDB)는 충돌을 예외적인 상황이 아닌 일상적인 일로 취급한다. 충돌한 문서가 여러 개여도 상관없다. 어차피 그중에 최종본이 있을 것이고, 나머지 문서는 Complication이 진행되면서 물리적으로 삭제되기 전까지는 접근이 가능하다. 충돌 문서도 복제, 보안 적용 등 모두 일반 문서와 똑같이 취급한다.[2]
- 데이터베이스 API = 예를 들어 'nchovy'라는 이름의 DB를 만들어 볼 때, 데이터베이스 이름으로 대문자가 허용되지 않는다. 만약 이미 존재하는 DB를 생성하려고 하면 오류 응답이 뜬다. 방금 만든 데이터베이스의 정보를 얻어오려면, URL에 데이터베이스 이름만 덧대어 단순히 GET 요청을 보내기만 하면 된다. 마지막으로 데이터베이스를 삭제하려는 경우에는 HTTP의 DELETE 메소드를 사용하면 된다. 데이터베이스 정보를 볼 때는 GET, 생성은 PUT, 삭제는 DELETE, 이렇게 REST 스타일을 충실히 따르고 있음을 알 수 있다. 이는 문서의 경우에도 마찬가지로 적용된다.
- 문서 API = 예를 들어 'nchovy' 라는 이름의 데이터베이스에 문서를 하나 만들어 볼 때, 이름을 주고 문서를 생성할 때는 아래와 같이 PUT 메소드를 사용하고, 서버에서 생성한 DoclD를 이용하려고 하는 경우에는 POST를 사용한다. 이렇게 문서를 생성한 뒤 조회를 해보면 _id와 _rev값이 추가 된다. 이는 카우치디비(CouchDB)에서 예약해서 사용하고 있는 필드이다. 앞으로 밑줄이 앞에 붙은 필드는 예약된 필드라고 생각하면 된다. 응답을 그대로 보기는 어려우므로 포맷을 수정한다. 업데이트 할 때도 동일하게 PUT 메소드를 사용하는데, 반드시 _rev 속성을 같이 넘겨줘야만 카우치디비(CouchDB)가 어느 버전을 기준으로 업데이트 할 것인지 판단할 수 있다. _rev를 넘기지 않는 경우 실제로는 conflict가 아니더라도 conflict 오류를 보게 된다. 삭제할 때도 역시 쿼리스트링으로 rev까지 붙여야한다. 삭제라고 하지만 실제로는 문서에 _deleted 필드를 true로 쓰는 것이다. 역시 삭제 또한 최신 버전을 기반으로 하지 않은 경우 conflict 오류가 뜬다. 따라서 이 상태에서 문서를 조회하면 아래와 같이 응답의 reason 속성이 missing으로 뜨지 않고 deleted로 뜨게 된다. 그러므로 문서를 복구하고 싶다면 속성만 제거해주면 된다. 실제 삭제는 Compact Database 명령을 강제로 내리거나 일정 크기 이상 삭제된 것이 쌓여서 자동으로 일괄 삭제가 되는 경우에 이루어진다. 문서에 첨부파일을 추가하는 것도 가능하다. 위에서 했던 방식대로 하면서 인라인으로 추가하는 것과 별도의 API를 사용해서 첨부하는 것도 가능하다. 첨부는 _attachments 속성으로 전달한다. _attachments에서 추측할 수 있겠지만 여기에 해시로 다시 파일 이름과 데이터 쌍이 들어간다. 파일 데이터는 content_type과 실제 데이터가 data 속성으로 들어간다. data는 BASE64인 코딩을 이용한다. 이제 첨부된 파일을 불러올 수 있다. 그렇지만 원본 파일을 인코딩 하지 않고 올리는 것이 아무래도 더 편한 방법이다. 아래와 같이 Content-Type 헤더에 MME 타입을 지정해서 PUT 메소드로 올리면 된다. URL에서 문서 뒷부분에 /파일이름?rev=버전을 붙여준다.
- 뷰 API = 뷰는 MapReduce 자바스크립트 함수로 정의된다. 뷰는 임시로 정의되는 것(Ad-Hoc View)과 한번 정의해놓고 계속 쓸 수 있는 것(Perm anent View)으로 나눠지는데, 후자는 특별히 설계 문서(Design Document)로 저장해둬야 한다. 설계 문서는 문서 ID가 _design/로 시작돼야 하고, map과 reduce 속성으로 뷰 함수를 정의한다. map은 필수적이지만 reduce는 옵션이다. NVD(National Vulnerability Database, 미 정부 기관 NIST에서 운영)의 CVE XML 스키마를 관계형 데이터베이스에 맞춰서 제대로 정규화 하면 17개나 되는 테이블을 맞달뜨리게 된다. 이런 경우에는 카우치디비(CouchDB) 같은 문서 기반 데이터베이스가 편리하다. 일일이 커맨드라인으로 입력하기는 불편하니 웹 관리 화면을 이용하는게 좋다.[2]
주요 특징
- Document Storage = 문자열로 이뤄진 JSON과 같은 형태의 Document를 저장한다.
- ACID Semantics = 동시성 제어가 가능하며, 많은 사용자가 읽어나, 쓸 때 충돌없이 동작이 가능하다.
- Map/Reduce Views and Indexes = 자바스크립트를 이용해 Map/reduce작업을 진행하여 값을 구할 수 있으며, 인덱스와 뷰 등을 생성하여 관리할 수 있다.
- Distributed Architecture with Replication = 양방향의 데이터 복제 허용하여, 복제된 DB에서 변경이 일어나더라도 다른 복제본과 데이터를 서로 동기화한다.
- REST API = URI를 기준으로 POST, GET, PUT, DELETE를 이용하여 CRUD 처리가 가능하다.
- Eventual Consistency = 분산형 컴퓨팅에서 사용되는 동시성 일관성 모델인 Eventual Consistency 보장
- Built for Offline = 스마트폰과 같은 기기에도 데이터 복제가 허용되며, 온라인 상황에서 데이터 동기화 실시[5]
다른 데이터베이스(DB)와의 차이점
카우치디비(CouchDB) 와 관계형 데이터베이스의 차이점
몽고DB와 카우치DB와 같은 문서형 저장소는 데이터를 테이블에 저장하지 않고 문서 형식으로 저장하여 모든 연관된 정보들을 세분화하여 분리하지 않고 JSON 형식으로 한 문서 안에 저장을 시킨다. 대표적인 예를 들면 HTML 형식으로 이루어진 웹 문서를 생각하면 된다. 이에 반해서 관계형 데이터베이스의 경우에는 정보들을 분류 가능한 최소 단위까지 세분화하여 세분화 된 정보들 간의 관계를 설정함으로써 데이터를 구조화 시킨다. 문서형 저장소는 데이터들 간의 요소들의 관계가 비교적 느슨하며, 새로운 데이터를 추가하기 위해서 모든 문서에 불필요한 공간을 생성할 필요가 없다. 이렇게 스카마 변경에 따른 어려움이 없다는 것이 카우치디비(CouchDB)와 같은 NoSQL의 큰 장점이다.[1]
카우치디비(CouchDB) 와 몽고DB의 차이점
같은 NoSQL이면서 문서지향 데이터베이스인 몽고DB와의 차이점에 대해서 말하자면 우선 몽고DB의 경우에는 커스텀 바이너리 프로토콜(Custom Binary Protocol)을 사용하기 때문에 몽고DB를 사용하기 위해서는 별도의 드라이버(Driver)가 필요한 반면에 카우치디비(CouchDB)의 경우에는 HTTP/REST 프로토콜을 사용함으로 별도의 드라이버가 필요없고 인터넷에 연결이 되어 있으면 HTTP를 이용하여 카우치디비(CouchDB)를 사용할 수 있다. 카우치디비(CouchDB)의 사상은 간단하다. 모든 정보를 문서형태로 저장한다. 이러한 문서들을 저장하는데 초점을 맞춘다. 이에 반해서 몽고DB의 경우에는 문서 형태로 저장하는 목적이외에 문서 저장소를 관리하는 계층을 두어서 저장소에 대한 세부적인 관리를 실시한다. 몽고DB에서 쿼리(Query)를 사용하여 문서를 빠르게 검색할 수 있다. 이러한 쿼리는 관계형 데이터베이스와 유사하여 관계형 데이터베이스 사용자들이 몽고DB를 손쉽게 사용할 수 있도록 유도하는 장점중에 하나다. 하지만 카우치디비(CouchDB)는 쿼리를 뷰에 대해서만 할 수 있으며, 이 뷰는 기본적으로 Map-reduce 함수를 기반으로 작동한다. Map-reduce는 분산 환경을 위해서 탄생하였지만 쿼리 속도가 느리다는 태생적 한계점을 가지고 있다. 카우치디비(CouchDB)를 적용하기 쉬운 분야로는 CRM, CMS와 같은 데이터를 누적하는 시스템 및 데이터의 변화가 별로 없는 시스템이며, 몽고DB는 동적인 질의가 필요한 시스템이나 빠른 조회를 위해서 인덱스 사용이 필요한 시스템에 사용하는 것이 적절하다.[1]
단점
- 메모리 내 DBMS보다 느리다.
- 적절한 업데이트를 수행하려면 서버 쪽 논리 (update handlers)이 필요하다.
- 디스크 대 속도의 거래 : 데이터베이스는 다른 DBMS에 비해 엄청나게 커질 수 있다.
- "유일한" 최종 일관성
- 대규모 데이터 세트의 임시보기는 매우 느리다.
- 대규모 데이터베이스 may fail 복제.
- 지도 / 축소 패러다임을 위해서는 재고가 필요하다.[6]
전망
밥 위더홀드 카우치베이스 최고경영자(CEO)는 지난해 300억달러로 추산된 세계 DB 시장에서 관계형 DB가 95%를 차지했지만 15년 뒤에는 NoSQL DB가 과반으로 늘어날 것이라고 전망했다. 한국에서도 소셜네트워크, 모바일, 이커머스 등 성장세가 빠른 시장에 NoSQL 도입이 늘 것이라고 전망했다.[7] 하지만 국내시장에서는 현재 관계형 데이터베이스(DB)보다 비정형데이터 저장에 특화된 NoSQL 기술에 대한 국내 시장의 관심이 시들해졌다. 빅데이터 플랫폼의 요소기술로 주목됐지만 오픈소스 진영과 글로벌 상용 소프트웨어(SW)업체마다 제각각 대응 기술을 내놓으면서 범용 솔루션을 원하는 사용자들을 끌어안지 못하는 상황이다. NoSQL은 '키-값' 형태의 저장구조를 취해 등장 초기부터 관계형 DB처럼 SQL 방식의 조회가 불가능하다는 특징을 그린 이름을 얻었다. 관계형 DB 이외 수단으로 저장하는 DB 기술을 통칭하지만, 공통점보다 차이점이 크다. NoSQL은 관계형 DB만으론 어려웠던 비정형데이터 저장을 보완하는 빅데이터 플랫폼의 구성요소로 주목됐다. 불과 3년 전만 해도 오픈소스 관계형 DB의 맹주 MySQL의 미래를 좌우할 중대변수 가운데 하나로 꼽혔고, 최근 1~2년간 신제품 등장과 업그레이드도 경쟁적으로 이뤄졌다. 하지만 정작 시장 반응은 뜨뜻미지근했다.[8]
각주
- ↑ 1.0 1.1 1.2 류프리, 〈카우치DB(Couch DB)〉, 《네이버블로그》 , 2017-08-12
- ↑ 2.0 2.1 2.2 2.3 yoontaesub, 〈정보수집〉, 《이글루스》 , 2010-10-09
- ↑ 위키백과, 〈얼랭〉, 《위키백과》 , 2019-01-08
- ↑ 4.0 4.1 양봉열, 〈데이터기술자료〉, 《kdata 한국데이터산업진흥원》
- ↑ 바라매, 〈CouchDB〉, 《네이버 블로그》, 2015-06-25
- ↑ 코드로그, 〈nosql-CouchDB의 단점〉, 《코드데이》 , 2019-05-12
- ↑ 임민철 기자, 〈카우치베이스-N2M, 국내 NoSQL시장 맞손〉, 《한국공개소프트웨어협회》 , 2013-03-13
- ↑ 임민철 기자, 〈NoSQL,도입사례 고픈 빅데이터 유망주〉, 《지디넷 코리아》 , 2013-01-21
참고 자료
- 류프리, 〈카우치DB(Couch DB)〉, 《네이버블로그》 , 2017-08-12
- 윤태섭, 〈정보수집〉, 《이글루스》 , 2010-10-09
- 위키백과, 〈얼랭〉, 《위키백과》 , 2019-01-08
- 양봉열, 〈데이터기술자료〉, 《kdata 한국데이터산업진흥원》
- 바라매, 〈CouchDB〉, 《네이버블로그》 , 2015-06-25
- 코드로그, 〈nosql-CouchDB의 단점〉, 《코드데이》 , 2019-05-12
- 임민철 기자, 〈카우치베이스-N2M, 국내 NoSQL시장 맞손〉, 《한국공개소프트웨어협회》 , 2013-03-13
- 임민철 기자, 〈NoSQL,도입사례 고픈 빅데이터 유망주〉, 《지디넷 코리아》 , 2013-01-21
같이 보기