카우치디비 편집하기

이동: 둘러보기, 검색

경고: 로그인하지 않았습니다. 편집을 하면 IP 주소가 공개되게 됩니다. 로그인하거나 계정을 생성하면 편집자가 아이디(ID)으로 기록되고, 다른 장점도 있습니다.

편집을 되돌릴 수 있습니다. 이 편집을 되돌리려면 아래의 바뀐 내용을 확인한 후 저장해주세요.
최신판 당신의 편집
14번째 줄: 14번째 줄:
  
 
== 기술 개요 ==
 
== 기술 개요 ==
 
+
* '''문서 저장소'''
=== 문서 저장소 ===
 
 
문서는 카우치디비에서 가장 기본적인 데이터 단위이며, 다수의 필드와 첨부 파일로 구성된다. 문서에는 [[데이터베이스]] 시스템이 관리하는데 필요한 메타데이터도 포함된다. 각 문서 필드는 유일한 이름을 가지고 있고, 텍스트, 숫자, 불린, 리스트 등 다양한 타입의 값을 사용할 수 있으며, 텍스트 크기나 필드 수에는 아무런 제약이 없다. CVS나 서브버전을 쓴다면 익숙하겠지만, 카우치디비의 업데이트 모델은 낙관적인 모델이라 잠금을 사용하지 않는다. 만약 여러 명의 사용자가 동시에 같은 문서를 편집하는 상황이 발생하면, 나중에 저장하는 사람은 충돌이 발생했음을 알게 된다. 나중에 넣는 최신 버전을 기반으로 다시 써서 넣게 되어있다.
 
문서는 카우치디비에서 가장 기본적인 데이터 단위이며, 다수의 필드와 첨부 파일로 구성된다. 문서에는 [[데이터베이스]] 시스템이 관리하는데 필요한 메타데이터도 포함된다. 각 문서 필드는 유일한 이름을 가지고 있고, 텍스트, 숫자, 불린, 리스트 등 다양한 타입의 값을 사용할 수 있으며, 텍스트 크기나 필드 수에는 아무런 제약이 없다. CVS나 서브버전을 쓴다면 익숙하겠지만, 카우치디비의 업데이트 모델은 낙관적인 모델이라 잠금을 사용하지 않는다. 만약 여러 명의 사용자가 동시에 같은 문서를 편집하는 상황이 발생하면, 나중에 저장하는 사람은 충돌이 발생했음을 알게 된다. 나중에 넣는 최신 버전을 기반으로 다시 써서 넣게 되어있다.
  
23번째 줄: 22번째 줄:
 
만약 1단계에서 운영체제 충돌이 발생하거나 전원 문제가 발생한다면, 해당 업데이트된 부분은 그냥 재시작할 때 잊힌다. 2단계에서 문제가 발생한다면, 이전의 동일한 헤더가 남아있으므로 이전에 커밋된 데이터가 일관성을 보장할 수 있다. 헤더 영역을 제외하고는 무결성 검사를 수행하거나 복구하는 작업이 전혀 필요 없다. 한편으로, 수정이나 삭제가 발생해도 계속 파일 끝에 추가만 하므로 낭비되는 공안이 상당히 발생한다. [[데이터베이스]] 정리를 하려면 Complication이 필요한데, 유효한 데이터만 모아서 복사본을 새로 쓰고, 다 쓰면 예전 파일을 버리는 방식으로 구현돼 있다. 적당한 시점에 [[데이터베이스]] 자체적으로 이 작업을 수행하긴 하지만 관리자가 직접 명령을 내릴 수도 있다.
 
만약 1단계에서 운영체제 충돌이 발생하거나 전원 문제가 발생한다면, 해당 업데이트된 부분은 그냥 재시작할 때 잊힌다. 2단계에서 문제가 발생한다면, 이전의 동일한 헤더가 남아있으므로 이전에 커밋된 데이터가 일관성을 보장할 수 있다. 헤더 영역을 제외하고는 무결성 검사를 수행하거나 복구하는 작업이 전혀 필요 없다. 한편으로, 수정이나 삭제가 발생해도 계속 파일 끝에 추가만 하므로 낭비되는 공안이 상당히 발생한다. [[데이터베이스]] 정리를 하려면 Complication이 필요한데, 유효한 데이터만 모아서 복사본을 새로 쓰고, 다 쓰면 예전 파일을 버리는 방식으로 구현돼 있다. 적당한 시점에 [[데이터베이스]] 자체적으로 이 작업을 수행하긴 하지만 관리자가 직접 명령을 내릴 수도 있다.
  
=== ===
+
* ''''''
 
카우치디비는 비정형 데이터를 다루기 위해 [[자바스크립트]] 함수를 이용해 데이터 가공을 처리한다. 이 [[자바스크립트]] 함수를 뷰라고 하는데 MapReduce 모델로 되어있다. 뷰는 카우치디비 문서를 인수로 하여 계산을 수행하면서 키-값 쌍을 추가한다. Reduce 함수가 정의되어 있다면 Map 함수에서 발생시킨 키-값 쌍을 받아서 키를 기준으로 데이터를 가공한다. 조회 결과는 Map 함수에서 넘겨준 키를 기준으로 정렬된다. 키 값이 null이면 문서 ID를 기준으로 정렬한다.<ref name="양봉열">양봉열, 〈[https://www.kdata.or.kr/info/info_04_view.html?field=&keyword=&type=techreport&page=221&dbnum=127630&mode=detail&type=techreport 데이터기술자료]〉, 《kdata 한국데이터산업진흥원》 </ref> 이렇게 처리하면 비정형 데이터라도 유연하게 처리할 수 있긴 하지만, 대용량 데이터를 처리하려면 인덱스가 필요하지 않나 생각할 것이다. 카우치디비는 뷰 인덱스를 지원한다. 뷰와 함수는 설계 문서 안에 저장되고, 설계 문서는 여러 개의 뷰 함수를 포함할 수 있게 되어있다. 같은 설계 문서에 포함된 뷰는 같은 그룹으로 인덱스된다. 인덱스는 문서 ID와 뷰 함수의 결괏값으로 구성된다. 뷰 빌더는 시퀀스 ID를 이용하여 [[뷰]] 그룹이 최신 버전인지 확인하고, 만약에 최신 버전이 아닌 경우에는 마지막 변경을 반영했던 시점 이후의 모든 문서를 추적한다. 이렇게 인덱스 갱신 작업이 일어나는 도중에도 읽기 문서를 모두 찾아서 반영하고 난 이후에는 뷰 인덱스에 있던 이전 값은 버려진다. 특정 문서가 뷰 함수에 의해 선택된 경우에는 그 함수의 결괏값이 인덱스에 반영된다. 인덱스 또한 파일 끝에 계속 추가되므로, 디스크 헤드를 최적으로 움직일 수 있으며 충돌이 발생하거나 전원이 나가는 경우에도 인덱스가 깨지는 일은 없다. 인덱스를 업데이트하는 도중에 충돌이 발생한다면 불완전한 인덱스 업데이트는 그냥 버려지고 마지막으로 커밋됐던 상태로부터 다시 인덱스가 만들어진다.<ref name="윤태섭"></ref>
 
카우치디비는 비정형 데이터를 다루기 위해 [[자바스크립트]] 함수를 이용해 데이터 가공을 처리한다. 이 [[자바스크립트]] 함수를 뷰라고 하는데 MapReduce 모델로 되어있다. 뷰는 카우치디비 문서를 인수로 하여 계산을 수행하면서 키-값 쌍을 추가한다. Reduce 함수가 정의되어 있다면 Map 함수에서 발생시킨 키-값 쌍을 받아서 키를 기준으로 데이터를 가공한다. 조회 결과는 Map 함수에서 넘겨준 키를 기준으로 정렬된다. 키 값이 null이면 문서 ID를 기준으로 정렬한다.<ref name="양봉열">양봉열, 〈[https://www.kdata.or.kr/info/info_04_view.html?field=&keyword=&type=techreport&page=221&dbnum=127630&mode=detail&type=techreport 데이터기술자료]〉, 《kdata 한국데이터산업진흥원》 </ref> 이렇게 처리하면 비정형 데이터라도 유연하게 처리할 수 있긴 하지만, 대용량 데이터를 처리하려면 인덱스가 필요하지 않나 생각할 것이다. 카우치디비는 뷰 인덱스를 지원한다. 뷰와 함수는 설계 문서 안에 저장되고, 설계 문서는 여러 개의 뷰 함수를 포함할 수 있게 되어있다. 같은 설계 문서에 포함된 뷰는 같은 그룹으로 인덱스된다. 인덱스는 문서 ID와 뷰 함수의 결괏값으로 구성된다. 뷰 빌더는 시퀀스 ID를 이용하여 [[뷰]] 그룹이 최신 버전인지 확인하고, 만약에 최신 버전이 아닌 경우에는 마지막 변경을 반영했던 시점 이후의 모든 문서를 추적한다. 이렇게 인덱스 갱신 작업이 일어나는 도중에도 읽기 문서를 모두 찾아서 반영하고 난 이후에는 뷰 인덱스에 있던 이전 값은 버려진다. 특정 문서가 뷰 함수에 의해 선택된 경우에는 그 함수의 결괏값이 인덱스에 반영된다. 인덱스 또한 파일 끝에 계속 추가되므로, 디스크 헤드를 최적으로 움직일 수 있으며 충돌이 발생하거나 전원이 나가는 경우에도 인덱스가 깨지는 일은 없다. 인덱스를 업데이트하는 도중에 충돌이 발생한다면 불완전한 인덱스 업데이트는 그냥 버려지고 마지막으로 커밋됐던 상태로부터 다시 인덱스가 만들어진다.<ref name="윤태섭"></ref>
  
=== 보안과 유효성 검증 ===
 
카우치디비는 누가 문서를 읽고 수정할 수 있는지 제한할 수 있도록, 간단하면서도 확장 가능한 보안 모델을 가지고 있다.
 
 
* '''관리자 접근''' : 관리자 계정의 경우 다른 관리자 계정을 만들거나 설계 문서를 수정할 수 있다.
 
 
* '''리더 접근''' : 카우치디비 문서에 리더(Reader) 목록을 저장한다. 따로 정의된 게 없으면 누구나 읽을 수 있지만, 목록이 정의되어 있는 경우에는 지정된 사용자만 읽기가 가능하다. 뷰에서 접근할 때도 이 접근 제한이 동일하게 적용된다. 접근 권한이 없으면 뷰 조회 결과에서 빠지도록 되어 있다.
 
 
* '''업데이트 접근''' : 디스크에 문서를 쓰는 시점에 [[자바스크립트]] 함수를 이용하여 유혀성 검증이 이뤄진다. 유효성 검증을 통과하면 그대로 업데이트를 진행할 수 있는 반면, 그렇지 않은 경우에는 작업이 중단되고 클리이언트는 오류 응답을 받는다. [[자바스크립트]] 함수의 매개변수로 사용자 계정 정보와 업데이트 한 문서가 같이 넘어오므로, 이 값을 이용하여 권한을 결정한다. 가령, 최초 작성자만 문서를 수정할 수 있도록 하고 싶다면 간단히 문서의 author 필드와 현재 사용자 계정을 비교하도록 코드를 작성하면 된다.<ref name="양봉열"></ref>
 
 
* '''분산 업데이트와 복제''' : 카우치디비는 P2P 기반의 분산 [[데이터베이스]] 시스템이기 때문에, 접속이 끊어진 상태에서도 공유 [[데이터]]를 접근하거나 수정할 수 있고, 양방향으로 변경 사항을 복제하는 것도 가능하다. 카우치디비는 마지막 복제가 일어났던 시점 이후에 변경된 문서만 복제한다. 도중에 복제가 실패하더라도 그냥 마지막으로 복제 전송하던 문서부터 다시 시작하면 된다. 특히 모든 변경이 파일 끝에 추가되는 형태로 이뤄지므로 복제에 필요한 디스크 헤드의 움직임도 효율적이다. 특정 기준을 만족하는 문서만 복제하도록 [[자바스크립트]] 함수로 필터링해서 일부분만 복제하는 것도 가능하다. 보통 오프라인 모드로 동작해야 할 때 전체 [[데이터베이스]]를 복제하기엔 너무 시간이 오래걸리므로, 실제 사용할 부분만 복제하도록 하는 용도로 쓴다. 분산 [[데이터베이스]] 시스템에서는 충돌을 탐지하고 관리하는 것도 중요한 문제이다. 카우치디비는 충돌을 예외적인 상황이 아닌 일상적인 일로 취급한다. 충돌한 문서가 여러 개여도 상관없다. 어차피 그중에 최종본이 있을 것이고, 나머지 문서는 Complication이 진행되면서 물리적으로 삭제되기 전까지는 접근이 가능하다. 충돌 문서도 복제, 보안 적용 등 모두 일반 문서와 똑같이 취급한다.<ref name="윤태섭"></ref>
 
 
* '''데이터베이스 API''' : 예를 들어 'nchovy'라는 이름의 DB를 만들어 볼 때, 데이터베이스 이름으로 대문자가 허용되지 않는다. 만약 이미 존재하는 DB를 생성하려고 하면 오류 응답이 뜬다. 방금 만든 [[데이터베이스]]의 정보를 얻어오려면, URL에 [[데이터베이스]] 이름만 덧대어 단순히 GET 요청을 보내기만 하면 된다. 마지막으로 [[데이터베이스]]를 삭제하려는 경우에는 HTTP의 DELETE 메소드를 사용하면 된다. [[데이터베이스]] 정보를 볼 때는 GET, 생성은 PUT, 삭제는 DELETE, 이렇게 REST 스타일을 충실히 따르고 있음을 알 수 있다. 이는 문서의 경우에도 마찬가지로 적용된다.
 
  
* '''문서 API''' : 예를 들어 'nchovy' 라는 이름의 [[데이터베이스]]에 문서를 하나 만들어 볼 때, 이름을 주고 문서를 생성할 때는 아래와 같이 PUT 메소드를 사용하고, 서버에서 생성한 DoclD를 이용하려고 하는 경우에는 POST를 사용한다. 이렇게 문서를 생성한 뒤 조회를 해보면 _id와 _rev값이 추가 된다. 이는 카우치디비에서 예약해서 사용하고 있는 필드이다. 앞으로 밑줄이 앞에 붙은 필드는 예약된 필드라고 생각하면 된다. 응답을 그대로 보기는 어려우므로 포맷을 수정한다. 업데이트 할 때도 동일하게 PUT 메소드를 사용하는데, 반드시 _rev 속성을 같이 넘겨줘야만 카우치디비가 어느 버전을 기준으로 업데이트 할 것인지 판단할 수 있다. _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=버전을 붙여준다.
+
* '''보안과 유효성 검증''' = 카우치디비는 누가 문서를 읽고 수정할 수 있는지 제한할 수 있도록, 간단하면서도 확장 가능한 보안 모델을 가지고 있다.
 +
#관리자 접근 = 관리자 계정의 경우 다른 관리자 계정을 만들거나 설계 문서를 수정할 수 있다.
 +
#리더 접근 = 카우치디비 문서에 리더(Reader) 목록을 저장한다. 따로 정의된 게 없으면 누구나 읽을 수 있지만, 목록이 정의되어 있는 경우에는 지정된 사용자만 읽기가 가능하다. 뷰에서 접근할 때도 이 접근 제한이 동일하게 적용된다. 접근 권한이 없으면 뷰 조회 결과에서 빠지도록 되어 있다.
 +
#업데이트 접근 = 디스크에 문서를 쓰는 시점에 [[자바스크립트]] 함수를 이용하여 유혀성 검증이 이뤄진다. 유효성 검증을 통과하면 그대로 업데이트를 진행할 수 있는 반면, 그렇지 않은 경우에는 작업이 중단되고 클리이언트는 오류 응답을 받는다. [[자바스크립트]] 함수의 매개변수로 사용자 계정 정보와 업데이트 한 문서가 같이 넘어오므로, 값을 이용하여 권한을 결정한다. 가령, 최초 작성자만 문서를 수정할 수 있도록 하고 싶다면 간단히 문서의 author 필드와 현재 사용자 계정을 비교하도록 코드를 작성하면 된다.<ref name="양봉열"></ref>
 +
#분산 업데이트와 복제 = 카우치디비는 P2P 기반의 분산 [[데이터베이스]] 시스템이기 때문에, 접속이 끊어진 상태에서도 공유 [[데이터]]를 접근하거나 수정할 수 있고, 양방향으로 변경 사항을 복제하는 것도 가능하다. 카우치디비는 마지막 복제가 일어났던 시점 이후에 변경된 문서만 복제한다. 도중에 복제가 실패하더라도 그냥 마지막으로 복제 전송하던 문서부터 다시 시작하면 된다. 특히 모든 변경이 파일 끝에 추가되는 형태로 이뤄지므로 복제에 필요한 디스크 헤드의 움직임도 효율적이다. 특정 기준을 만족하는 문서만 복제하도록 [[자바스크립트]] 함수로 필터링해서 일부분만 복제하는 것도 가능하다. 보통 오프라인 모드로 동작해야 할 때 전체 [[데이터베이스]]를 복제하기엔 너무 시간이 오래걸리므로, 실제 사용할 부분만 복제하도록 하는 용도로 쓴다. 분산 [[데이터베이스]] 시스템에서는 충돌을 탐지하고 관리하는 것도 중요한 문제이다. 카우치디비는 충돌을 예외적인 상황이 아닌 일상적인 일로 취급한다. 충돌한 문서가 여러 개여도 상관없다. 어차피 그중에 최종본이 있을 것이고, 나머지 문서는 Complication이 진행되면서 물리적으로 삭제되기 전까지는 접근이 가능하다. 충돌 문서도 복제, 보안 적용 등 모두 일반 문서와 똑같이 취급한다.<ref name="윤태섭"></ref>
  
* '''뷰 API''' : 뷰는 맵리듀스(MapReduce) [[자바스크립트]] 함수로 정의된다. 뷰는 임시로 정의되는 것(Ad-Hoc View)과 한번 정의해놓고 계속 쓸 수 있는 것(Perm anent View)으로 나눠지는데, 후자는 특별히 설계 문서(Design Document)로 저장해둬야 한다. 설계 문서는 문서 ID가 _design/로 시작돼야 하고, map과 reduce 속성으로 뷰 함수를 정의한다. map은 필수적이지만 reduce는 옵션이다. NVD(National Vulnerability Database, 미 정부 기관 NIST에서 운영)의 CVE XML 스키마를 관계형 [[데이터베이스]]에 맞춰서 제대로 정규화 하면 17개나 되는 테이블을 맞달뜨리게 된다. 이런 경우에는 카우치디비 같은 문서 기반 데이터베이스가 편리하다. 일일이 커맨드라인으로 입력하기는 불편하니 웹 관리 화면을 이용하는게 좋다.<ref name="윤태섭"></ref>
+
#데이터베이스 API = 예를 들어 'nchovy'라는 이름의 DB를 만들어 볼 때, 데이터베이스 이름으로 대문자가 허용되지 않는다. 만약 이미 존재하는 DB를 생성하려고 하면 오류 응답이 뜬다. 방금 만든 [[데이터베이스]]의 정보를 얻어오려면, URL에 [[데이터베이스]] 이름만 덧대어 단순히 GET 요청을 보내기만 하면 된다. 마지막으로 [[데이터베이스]]를 삭제하려는 경우에는 HTTP의 DELETE 메소드를 사용하면 된다. [[데이터베이스]] 정보를 볼 때는 GET, 생성은 PUT, 삭제는 DELETE, 이렇게 REST 스타일을 충실히 따르고 있음을 알 수 있다. 이는 문서의 경우에도 마찬가지로 적용된다.
 +
#문서 API = 예를 들어 'nchovy' 라는 이름의 [[데이터베이스]]에 문서를 하나 만들어 볼 때, 이름을 주고 문서를 생성할 때는 아래와 같이 PUT 메소드를 사용하고, 서버에서 생성한 DoclD를 이용하려고 하는 경우에는 POST를 사용한다. 이렇게 문서를 생성한 뒤 조회를 해보면 _id와 _rev값이 추가 된다. 이는 카우치디비에서 예약해서 사용하고 있는 필드이다. 앞으로 밑줄이 앞에 붙은 필드는 예약된 필드라고 생각하면 된다. 응답을 그대로 보기는 어려우므로 포맷을 수정한다. 업데이트 할 때도 동일하게 PUT 메소드를 사용하는데, 반드시 _rev 속성을 같이 넘겨줘야만 카우치디비가 어느 버전을 기준으로 업데이트 할 것인지 판단할 수 있다. _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개나 되는 테이블을 맞달뜨리게 된다. 이런 경우에는 카우치디비 같은 문서 기반 데이터베이스가 편리하다. 일일이 커맨드라인으로 입력하기는 불편하니 웹 관리 화면을 이용하는게 좋다.<ref name="윤태섭"></ref>
  
 
== 주요 특징 ==
 
== 주요 특징 ==

해시넷에서의 모든 기여는 다른 기여자가 편집, 수정, 삭제할 수 있다는 점을 유의해 주세요. 만약 여기에 동의하지 않는다면, 문서를 저장하지 말아 주세요.
또한, 직접 작성했거나 퍼블릭 도메인과 같은 자유 문서에서 가져왔다는 것을 보증해야 합니다 (자세한 사항은 해시넷:저작권 문서를 보세요). 저작권이 있는 내용을 허가 없이 저장하지 마세요!

취소 | 편집 도움말 (새 창에서 열림)