컬럼 데이터 형식을 어떻게 결정하는가? 직감으로 보면 알 수 있나? 아니면 기준을 가지고 하는가? 훈련 없는 직감은 아차 하는 실수를 낳을 수 있다. 기준을 가지고 컬럼 데이터 형식을 판단하는 훈련을 여러 번 하다 보면 훈련 받은 직감이 생긴다. 이 직감은 객관적인 판단기준이 휘리릭 ~ 하고 계산되고 나온 결과이다. 여러분이 이러한 훈련을 하는데 필요한 기준이 무엇인지를 설명한다.
 
첫 번째 기준은 “논리적인 데이터 형식 먼저 생각하라.” 논리적인 데이터 형식이라는 것은 해당 컬럼 혹은 변수가 문자(character), 숫자(Number), 날짜(Datetime), 바이너리(Binary) 카테고리 중 어디에 속하는가 이다.
 
모든 데이터 형식들은 이 4가지 범주 안에 반드시 속하게 되어있다. 그리고 이 4가지 데이터 형식들은 모두 문자열(String)에서 파생되었다. 따라서 모든 값은 문자열로 표현될 수 있다. 예를 들어 숫자도 문자열로 표현될 수 있다. 2는 정수(Int) 값이지만 ‘02’ 혹은 ‘2’ 라는 문자열 값으로 저장할 수 있다. ‘2008-01-01’도 문자열로 저장이 가능하며, ;0x080000000100’ 와 같은 바이너리도 문자열로 저장할 수 있다. 따라서 모든 데이터 형식은 문자열이라고 볼 수 있고, 이를 Char 혹은 Varchar 형식에 저장할 수 있다. 원래의 데이터베이스는 이렇게 모든 데이터 형식을 문자열 형식 하나에 저장하였다. 하지만 시간이 흐르면서 여러 가지 형태의 데이터 형식들을 필요로 하게 되면서 이렇게 세분화된 것이다.
 

문자열 보다 다른 데이터 형식을 사용하는 첫 번째 이유는 문자열 보다 적은 바이트 수로 값을 표현할 수 있다는 것이다. ‘10000’은 문자열로는 5바이트이지만, 정수형으로는 4바이트로 표현 가능하다. 날짜도 마찬가지이다. 두 번째 이유는 데이터 연산을 위해서 데이터 형식을 규정 짓고 싶기 때문이다.
 

정수는 사칙연산을 포함해서 다양한 수식 연산을 할 수 있고, 날짜도 더하거나 뺄 수 있다. 하지만 문자열은 고작해야 문자열을 자르고, 붙이고, 치환하는 작업만이 가능할 뿐이다. 내재적인 숫자 타입들은 이러한 연산 보다는 더하고, 빼고, 나누고, 곱하는 연산이 더 중요하다. 따라서 세분화된 데이터 형식은 문자열로는 수행하기 힘든 다양한 형식들을 이용할 수 있다. 
 

세 번째는 데이터 형식 각각이 값에 대한 검사 조건(Check constraint)으로서 사용된다는 것이다. 예를 들어, 정수형 컬럼에 ‘가나다’ 라는 문자열을 포함할 수는 없다. 오직 정수 형식의 데이터들만이 포함될 뿐이다. 날짜 형식도 마찬가지이다. 날짜 커럼에는 날짜 데이터 형식에 맞는 값들만이 들어갈 뿐이고, 벗어나는 값들은 오류를 발생한다. 이러한 검사 조건으로 컬럼형식을 사용할 수 있다.
 
① 적은 바이트 수로 값을 표현
② 연산 가능한 값을 위해
③  값의 형식 검사를 위해


참고서적:Deep Inside T-SQL


크리에이티브 커먼즈 라이센스
Creative Commons License 이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
트랙백 주소: http://ggoma.isblog.net/trackback_post_448.aspx

댓글을 달아 주세요

앞의 내용에서 사용한 힙 테이블을 기준으로 클러스터드 인덱스만을 가지는 테이블을 만들어 보자. 방법은 넌 클러스터드의 경우와 유사하며, 다만 리프 레벨 페이지가 직접 데이터 페이지가 된다는 점만이 다르다.


그림을 보면 실제 데이터 페이지 자체를 정렬해서 이것이 리프 레벨노드로 사용되고, 그 위에 넌 리프 레벨 노드와 루트 노드가 생성되었음을 알 수 있다. 데이터 페이지가 많지 않아 중간 레벨 노드들은 없었다. 기존 RID 대신에 최종 레벨 노드들은 인덱스 페이지의 첫 번재 로우값을 가지게 된다. 포인터 점프 대신에 데이터 페이지의 첫 첫번째 컬럼 값을 가지고 해당 데이터 페이지로 이동하여 데이터 페이지의 첫 번째 값부터 순차적으로 읽어 들이게 된다.
 
클러스커드 인덱스에서 값을 조회해보자. 검색할 데이터는 번호가 4번인 데이터이다. 역시 데이터 검색은 루트 노드부터 시작하게 된다. 루트노드에서 값이 4번인 것은 데이터 페이지 1에 있다는 것을 알 수 있다. 중간 레벨 노드들이 많았다면 아마도 몇 가지 레벨에 거쳐서 이러한 탐색 단계들을 진행해야만 할 것이다.


참고서적:Deep Inside T-SQL

크리에이티브 커먼즈 라이센스
Creative Commons License 이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
트랙백 주소: http://ggoma.isblog.net/trackback_post_447.aspx

댓글을 달아 주세요

아래<그림>은 클러스터드 인덱스가 없는 넌 클러스터드를 나타낸다. 첫 번째 레벨과 두 번째 레벨까지의 노드들은 인덱스 페이지들이며, 가장 하위의 것은 트리의 리프 레벨 노드들이 아니라 무작위로 저장되어 있는 데이터 페이지들이다. 그림에서는 intermediate레벨 노드들은 존재하지 않고 루트 노드와 리프노드로 직접 구성되어 있다. 리프 노드들은 데이터 페이지로의 포인터(이를 Row ID 혹은 RID(Relative Identifier)라고 부르기도 한다.)를 링크하고 있다.


이렇게 실제 데이터를 가져오기 위해서 리프 노드에서 데이터 페이지로 이동하는 것을 포인터 점프(Pointer Jump)라고 부른다. 클러스터드 인덱스에서는 리프 레벨이 데이터 페이지이므로 이러한 포인터 점프가 필요 없다. 포인터 점프는 쿼리의 퍼포먼스에 영향을 미칠 수도 있다.
 

좀더 실제적인 예를 가지고 살펴보자 우선 클러스터드든 넌 클러스터드든 아무런 인덱스도 설정되지 않은<그림1>와 같은 테이블이 있다. 이렇게 아무런 인덱스도 설정되지 않은 테이블을 힙 테이블(Heap Table)라고 한다. 데이터는 정렬되어 있지 않고 무작위로 들어 있다. 컬럼은 번호, 이름, 주소, 나이 순으로 되어 있고, 모두 11개의 로우들로 구성되어 있는 간단한 주소 테이블이다.
 

                                                            <그림1>

이 테이블의 데이터 페이지 구성은 <그림2>과 같으며, 무작위 순으로 되어 있다. (예제를 간단히 표현하기 위해서 하나의 데이터 페이지에는 모두 4개의 로우들만 들어 갈 수 있다고 가정 하였다)

                                                             <그림2>
 
모두 3개의 데이터 페이지로 구성되어 있으며 페이지 1과2는 모두 채워져 있고 페이지 3은 여분으로 로우 하나가 더 추가 될 수 있다.(실제 상황이라면 이렇게 로우 개수를 기준으로 데이터 페이지가 채워지는 것은 아니다. 당연히 로우가 가진 실제 데이터에 맞추어서 저장 되어진다.)
 
데이터 페이지에 저장된 것은 컬럼의 정열 순서와는 아무런 상관도 없다. 순전히 데이터가 추가된 순서라고 할 수 있다. 예를 들어 번호가 1인 김수영 로우가 가장 먼저 Insert 되고, 다음에 5번인 김시원 데이터가 저장되었다.
 
이를 번호 순으로 정렬하면 <그림3>과 같은 리스트가 만들어진다. 옆에 있는 Row ID는 앞에 강좌에서 설명한 것처럼 데이터 파일, 데이터페이지 번호, 로우 번호로 구성되는 것으로써 그림에서 번호 2의 경우 ‘1-1-1’ 1번 데이터 파일에 1번 페이지의 1번 로우라는 데이터 페이지 포인터 값을 가상으로 표현한 것이다. 일단 정렬된 리스트가 있으면 이를 인덱스 페이지로 구성하는 것은 매우 간단하다.

 

넌 클러스터드 인덱스를 만드는 것은 어려운 일이 아니다. 인덱스 컬럼으로 선택된 컬럼들을 기준으로 데이터 페이지를 정렬해서 이를 B-Tree로 구성하고, 리프 레벨에서는 실제 데이터페이지로 링킹 될 수 있도록 RID를 매팅한다.


클러스터드 인덱스가 없는 넌 클러스터드 인덱스 상황에서 원하는 값을 찾을 때 어떻게 수행되는지를 알아보자. 인덱스의 구축은 최하위 레벨인 리프 레벨에서부터 루트 노드로 상향식으로 이루어졌지만, 탐색은 정반대로 루트 노드에서 리프 레벨 노드로 이루어진다. 우선 번호 15를 가지고 루트 노드의 로우들과 비교한다. 

여기서 로우 아이디를 기준으로 포인터 점프(Pointer Jump)를 한다. 포인터 점프는 데이터 페이지로 링크하는 것이다.




크리에이티브 커먼즈 라이센스
Creative Commons License 이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
트랙백 주소: http://ggoma.isblog.net/trackback_post_446.aspx

댓글을 달아 주세요


인덱스는 클러스터드 인덱스와 넌 클러스터드 인덱스 두가지로 나눌 수 있다. 두 인덱스 간의 차이는 클러스터드 인덱스는 테이블 당 오직 하나만 생성할 수 있고, 넌 클러스터드 인덱스는 여러 개를 생성할 수 있다는 것이다.
 
인덱스는 모든 데이터를 B-Tree 형식으로 정렬해 놓게 된다. 클러스터드 인덱스는 키로 설정된 컬럼을 B-Tree 형식으로 정렬해 놓는데, 리프 레벨에서 아예 데이터 페이지 자체를 정렬해 놓는다. 이에 반해서 넌 클러스터드 인덱스는 별도의 인덱스 페이지를 생성하여 이를 관리한다. 그러므로 데이터 페이지 자체를 정렬해놓는 클러스터드 인덱스는 더 이상 추가가 불가능한 것이다.
 
인덱스 페이지의 구조는 이렇게 해당 테이블에 클러스터드 인덱스가 있느냐 아니면 넌 클러스터드 인덱스이냐에 따라 다른 구조를 띄게 된다. 좀더 정확히는 클러스터드 인덱스가 없는 넌 클러스터드 인덱스냐, 클러스터드가 있는 넌 클러스터드 인덱스이냐에 상관하는 형태를 가진다는 것이다. 


참고서적:Deep Inside T-SQL


크리에이티브 커먼즈 라이센스
Creative Commons License 이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
트랙백 주소: http://ggoma.isblog.net/trackback_post_439.aspx

댓글을 달아 주세요

IL.NET 2/26/2008 12:39:42 AM

#5 인덱스 아키텍처

데이터베이스에서 인덱스(Index)는 조회 성능을 높이기 위해서뿐만 아니라 유니크나 프라이머리 키 제약과 같은 제약 조건을 구현하기 위해서도 사용된다. 따라서 데이터베이스에서 인덱스를 논외로 하고서는 데이터 저장 구조를 논할 수 없다. 게다가 데이터 페이지조차도 이러한 인덱스 아키텍처에 매우 밀접한 영향을 받는다.
 

인덱스 또한 데이터이므로 별도의 인덱스 페이지로 관리된다. 다만, 데이터 페이지의 경우에는 링크드 리스트 보다 정확히는 더블 링크드 리스트(Double linked list)로 관리되는 반면, 인덱스 페이지는 B-Tree(Balanced tree or Binary tree)로 관리된다. B-Tree자료구조의 특징은 마치 나무를 거꾸로 뒤집어 놓은듯한 모양의 삼각형 구조로 기본이 되는 루트 노드를 중심으로 노드들이 하위로 연결되어 있으며 어떤노드에 대한 탐색 횟수도 동일한 균형 탐색구조를 가진다는 특징을 하고 있어서, 빠른 검색을 요하는 경우에 적합하다.

 
아래 <그림>이 인덱스의 B-Tree 구조를 나타내고 있다. 사각형으로 박스 처리된 A에서 H까지가 각각 노드들이다. 그리고 이 노드들은 Root Node Level, Intermediate Level(중간수준), Leaf Level의 세가지 레벨 수준들을 가진다.


최상위 층이 Root Node Level이고 가장 마지막에 있는 것이 Leaf Level이다. 중간에 위치하는 여러 수준들이 Intermediate Level이다. Root Node는 위에 아무런 노드가 없는 것이고 Leaf Level Node들은 더 이상 아래에 아무런 노드들이 없다.


 
이러한 B-Tree의 장점은 원하는 노드를 찾는데 빠르고, 일정한 시간 내에 처리할 수 있다는 것이다. 예를 들어 H 노드가 가진 “훈스닷넷”라는 값을 찾아보자. 우선 가장 루트 노드를 검색해서 조건을 비교하게 된다. A노드에서 B, C, D 노드들과 검색어인 훈스닷넷를 비교하면 B와 C노드에는 값이 없다.

 
다만 “훈” 이라는 문자는 D노드의 범위에 들어가기 때문에 D노드로 포인터를 이동한다. D노드에서 다시 하위 노드들을 비교하면 H노드에 실제 원하는 값이 있는지를 찾아낼 수가 있다. D노드의 자식 노드들은 모두 정렬이 되어있기 때문에 “훈스닷넷” 라는 제목을 가지는 데이터들이 여러 개 있다면 다음 노드들을 모두 탐색해보면 된다. 기껏해야 D노드 하위의 노드들을 모두 탐색해내는 것이기 때문에 테이블 스캔과 같이 A노드에서 H노드까지 모두 검색해낼 필요가 없기 때문에 빠르게 원하는 노드로 접근할 수 있다.

참고서적:Deep Inside T-SQL




크리에이티브 커먼즈 라이센스
Creative Commons License 이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
트랙백 주소: http://ggoma.isblog.net/trackback_post_437.aspx

댓글을 달아 주세요

IL.NET 2/19/2008 12:24:39 AM

#4 페이지의 구조

8KB로 이루어진 페이지는 이중에서 96바이트가 페이지 헤더로 사용되고, 나머지는 데이터를 저장한다고 하였다. 그럼 실제 페이지의 모습이 어떤지를 살펴보자.

데이터 페이지는 text, ntext, image와 같은 BLOB(Big Large Objects)형의 대형 데이터들을 제외한 모든 데이터들을 저장할 수 있다. BLOB데이터들은 데이터의 크기가 매우 크기 때문에 8KB의 제한을 가지는 일반 데이터페이지들은 저장할 수 없으며, 별도의 페이지에 저장된다.
 

페이지 헤더 영역은 각 페이의 앞/뒤 노드(Node)들에 대한 페이지 포인터 값을 가지고 있는데, 이는 자료구조에서 링크드 리스트(Linked List) 구조로 되어 있다. 앞에 노드가 없으면 null값을 가지며 앞의 노드나 뒤의 노드가 있으면 그 노드의 포인터 값들이 페이지 헤더에 저장된다. 
 

Data Row영역은 실제 데이터가 저장된다. 그림에서는 Data Row1과 Data Row2가 존재하고 있지만, 어떤 경우에는 하나의 데이터 로우가 페이지 전체를 사용할 수도 있다.
 

그 다음에 위치하는 것은 데이터 로우 및 페이지 헤더가 8KB의 데이터 페이지를 모두 사용하지 않았다면 아직 사용되지 않고 남아 있는 여유 공간이 된다. 인덱스 페이지에 보면 Fill Factor라는 것이 있다. 데이터 업데이트 작업이 빈번하게 일어나는 테이블의 경우에 인덱스의 성능 향상을 위해 일부러 페이지에 여유 공간을 두는 것인데, 이때 남아 있는 여유 공간이 바로 이덱스 페이지 Free Space이다.
 

이 공간을 모두 채워서 쓰게 되면 새로운 데이터를 추가할 때마다 새로운 인덱스 페이지를 생성하게 되므로(페이지 스플릿), 인덱스 페이지 생성에 너무 많은 비용이 소요된다. 
 

여유 공간의 마지막에는 로우 오프셋(Row Offset)이 존재한다. 오프셋은 단어 그대로 각 행의 첫째 바이트가 페이지의 시작에서 얼마나 떨어져 있는가 하는 위치 값을 가지고 있으며, 데이터 로우의 순서와는 반대로 끝에서부터 저장된다.

참고서적:Deep Inside T-SQL




크리에이티브 커먼즈 라이센스
Creative Commons License 이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
트랙백 주소: http://ggoma.isblog.net/trackback_post_434.aspx

댓글을 달아 주세요

실제 페이지는 Data, Index, Text/Image, GAM(Global Allocation Map), Page Free Space, IAM(Index Allocation Map) Bulk Changed Map, DCM(Differential Changed Map)의 모두8가지 종류로 되어 있다. 이후에 페이지라고 지칭하면 데이터 페이지 혹은 인덱스 페이지임을 기억한다. 나머지 페이지들은 데이터 저장의 용도로 사용되기 보다는 SQL서버에서 여유 공간 및 익스텐트 할당 등의 관리 용도로 사용되는 특수 페이지들이다. 물론 Text/Image는 BLOB데이터를 저장하는데 사용된다.
 
익스텐트들은 익스텐트 내의 페이지 구성에 따라 단일 익스텐트와 혼합 익스텐트로 나눌 수 있다. BOL에서는 이를 균일 및 혼합이라고도 부른다. 단일 익스텐트는 익스텐트내에 페이지들이 모두 동일한 테이블의 데이터를 저장하는 경우이고, 혼합 익스텐트는 그 반대로 하나의 익스텐트 안에 여러 테이블의 데이터를 저장한다. 이를 결정하는 기준은 저장할 데이터의 크기이다. 만일 데이터가 하나의 익스텐트, 즉 8개의 페이지를 모두 사용할 정도로 크다면 단일 익스텐트를 사용하고 그렇지 않다면 혼합익스텐트를 이용한다. 
  

참고서적:Deep Inside T-SQL




크리에이티브 커먼즈 라이센스
Creative Commons License 이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
트랙백 주소: http://ggoma.isblog.net/trackback_post_433.aspx

댓글을 달아 주세요

물리적인 데이터 베이스에 대해서 알았다면 보다 그 내부로 들어가보자. 물리적인 데이터베이스 안에는 여러
구성요소가 있지만, 데이터 저장과 관련해서 가장 기초가 되는 개념은 데이터 페이지와 익스텐트이다.
 
전산 개론이나 컴퓨터 시스템과 같은 과목을 들으면 디스크 시스템의 저장구조에 대해서 설명한다. 디스크는 섹터(Sector)와 트랙(Track)으로 구성되어 있다. 섹터는 디스크 상의 최소 물리적 저장 공간으로 보통 512바이트이며, 트랙은 이러한 동심원으로 그리며 이러한 색터를 포함하고 있다. 마찬가지로 데이터베이스에서는 페이지와 익스텐트라는 개념으로 사용되고 있다. 페이지는 디스크의 섹터와 유사한 개념이고, 익스텐트는 트랙과 유사한 개념이다.

섹터: http://www.terms.co.kr/sector.htm
트랙: http://www.terms.co.kr/track.htm
 
페이지에 대해서 알아보자. 페이지는 8KB로 이루어져 있다. 1KB가 1024바이트이므로, 8x1024해서 8192바이트가 하나의 페이지이다. 8192바이트 모두를 데이터 저장하는데 사용하지는 않고, 이중에서 96바이트는 페이지 헤더 정보를 저장하는데 사용한다. 그러므로 8096바이트를 실제 데이터를 저장하는 데에 사용한다. 대략 1메가 바이트당 128개의 페이지를 가질 수 있으며, 데이터 파일의 크기가 10메가 바이트라면 1280개의 페이지가 존재할 수 있다.
 
페이지는 SQL서버가 데이터를 저장하고 처리하는 데에 사용되는 가장 작은 입출력 단위이다. 이에 반해 익스텐트는 테이블과 인덱스를 저장하고 관리하는데 사용되는 공간의 기본 할당 단위이다. 하나의 익스텐트는 8개의 페이지들이 모여서 이루어진다. 정리하면 페이지는 데이터 처리의 최소 단위이고, 익스텐트는 공간 할당의 기본 단위이다. 


참고서적:Deep Inside T-SQL




크리에이티브 커먼즈 라이센스
Creative Commons License 이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
트랙백 주소: http://ggoma.isblog.net/trackback_post_432.aspx

댓글을 달아 주세요

우리가 사용하는 데이터베이스 즉 SQL서버에 있는 데이터는 어디에 저장할까요? SQL 서버가 버퍼 캐시(Buffer cache)에 자주 사용되는 데이터들을 저장해두기는 하지만, 실제 대부분의 데이터들은 파일 시스템에 저장해둔다. 이는 매우 당연한 것이다. 우리가 알고자 하는 것은 SQL서버가 어떻게 이러한 데이터들을 파일로 관리하는가 이다.
 
SQL 서버의 경우에 데이터베이스는 모두 세 가지 형식의 데이터 파일들을 유지하고 있다. 각각 MDF(Master Data File), NDF(secondary Data File), LDF(Log Data File)이다. SQL 서버가 설치된 폴더의 Data폴더를 보면 이러한 데이터 파일들이 있는 것을 알 수 있다. 혹은 master데이터베이스의 Sysdatabases 테이블을 검색하여 filename 컬럼을 확인해도 데이터베이스가 사용하는 파일을 볼 수 있다. 각각의 파일들은 다음과 같은 의미를 가진다.
- MDF(primary Data File): 주 데이터 파일
- NDF(secondary Data File): 보조 데이터 파일
- LDF(Log Data File): 로그 데이터 파일
 
새로운 데이터 베이스를 생성하면 MDF와 LDF 파일은 반드시 같이 생성된다. NDF는 사용자가 옵션을 지정하여 여러 파일들에 데이터를 분산 배치하고자 하는 경우에 이용될 수 있다. 참고로 NDF에 분산 배치는 데이터 I/O병목 현상을 줄이고, 수행 속도를 빠르게 할 수 있다.
 
우리가 사용하는 데이터베이스는 논리적인 데이터베이스와 물리적인 데이터 베이스로 나눌 수가 있다. 논리적인 데이터 베이스는 우리가 SQL Management를 이용하여 생성하는 테이블, 뷰, 저장 프로시져, 저장 함수 등을 논리적인 데이터베이스라고 한다.
 
그리고 앞에서 말한 MDF파일(Sample.mdf), NDF파일(Sample.ndf), LDF파일(Sample.ndf)는 실제 데이터베이스 정보 및 개체들을 가지고 있고, 물리적으로 실존한다는 의미에서 물리적인 데이터베이스이다. 물론 물리적인 데이터베이스는 파일이 아니라 파일이 그룹핑된 디바이스(Device)일 수도 있다.


참고서적:Deep Inside T-SQL


크리에이티브 커먼즈 라이센스
Creative Commons License 이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
트랙백 주소: http://ggoma.isblog.net/trackback_post_431.aspx

댓글을 달아 주세요