Read committed VS Repeatable read

POSTGRESQL 2019. 12. 18. 11:32

postgreSQL은 SQL 표준 트랜잭션 고립화 수준 중 Read committed, Repeatable read, Serializable 를 지원합니다. 그중 Read committed와 Repeatable read의 차이에 대해 정리해 보려 합니다.

 

1. Read committed : postgreSQL 의 default 고립화수준으로 begin 명령으로 트랜잭션을 시작하면 Read committed 모드가 적용됩니다. 쿼리가 시작되기 전 다른 트랜잭션에서 commit 된 결과를 참조할 수 있으며 동일 트랜잭션 상에서 동일한 쿼리문에 대해 서로 다른 결과를 조회(Phantom-Read)할 수 있습니다.

다시말해, 트랜잭션 내에서 같은 SELECT문을 실행하더라도 다른 트랜잭션의 commit 여부에 따라 다른 결과가 출력됩니다.

 

< 테스트 >

- TRANSACTION #1

postgres=# SHOW default_transaction_isolation;
 default_transaction_isolation 
-------------------------------
 read committed
(1 row)

postgres=# create table transaction_isolation_test(c1 int);
CREATE TABLE
postgres=# insert into transaction_isolation_test select 1 from generate_series(1,10) as n;
INSERT 0 10
postgres=# begin;
BEGIN
postgres=# select sum(c1) from transaction_isolation_test ;
 sum 
-----
  10
(1 row)

- TRANSACTION #2에서 1건 삽입

postgres=# begin;
BEGIN
postgres=# insert into transaction_isolation_test values(1);
INSERT 0 1
postgres=# commit;
COMMIT

- TRANSACTION #1에서 재조회 : TRANSACTION B에서 insert 한 결과가 반영됩니다.

postgres=# select sum(c1) from transaction_isolation_test ;
 sum 
-----
  11
(1 row)

 

 

2. Repeatable read : 트랜잭션이 시작되기 전(쿼리가 시작되기 전이 아닌) commit 된 결과를 참조합니다. phantom-read 를 불허(mysql은 SELECT는 안되는데 UPDATE는 됨.)하므로 트랜잭션 내 같은 쿼리문은 트랜잭션이 시작된 시점의 데이터 스냅샷 정보를 조회하기 때문에 다른 트랜잭션의 commit 여부와 무관하게 항상 같은 결과가 출력됩니다.

 

<테스트>

- TRANSACTION #1

postgres=# truncate table transaction_isolation_test ;
TRUNCATE TABLE
postgres=# insert into transaction_isolation_test select 1 from generate_series(1,10);
INSERT 0 10
postgres=# begin transaction isolation level repeatable read;
BEGIN
postgres=# select sum(c1) from transaction_isolation_test ;
 sum 
-----
  10
(1 row)

- TRANSACTION #2

postgres=# begin;
BEGIN
postgres=# insert into transaction_isolation_test values(1);
INSERT 0 1
postgres=# commit;
COMMIT

- TRANSACTION #1 : TRANSACTION B에서 커밋한 결과가 반영되지 않고 커밋전의 조회결과와 동일한 결과를 반환합니다.

postgres=# select sum(c1) from transaction_isolation_test ;
 sum 
-----
  10
(1 row)

 

<Read committed VS Repeatable read>

 

OLAP 환경에서 보고서를 작성할때 다른 트랜잭션에서 발생하는 데이터의 추가/삭제/변경분이 이슈원인이 될 수 있는 상황이라면 Repeatable read 모드를 통한 해결이 가능합니다.

window secureCRT 로 GCP 연결하기

BIG-DATA 2018. 12. 28. 10:38

1. window Git 또는 puttygen과 같이 윈도우 기반 ssh키를 생성해주는 유틸을 먼저 설치해야 합니다.

(window ssh-keygen 으로 검색하시면 됩니다.)

저같은 경우 window git이 설치되어 있어 git bash를 사용했습니다.



2. rsa 타입으로 공개키와 비밀키를 생성할때 comment(-C) 옵션으로 메일정보를 입력합니다.

GCP에 공개키 저장 시 콘솔상에서 계정명을 식별하는데 코멘트 정보가 필요합니다.(물론 코멘트 정보를 따로 타이핑하셔도 됩니다.)

$ ssh-keygen -t rsa -C "kimminsirk@gmail.com"
  
  

C:\user\계정명\.ssh 하위에 생성된 공개키(id_rsa.pub) 를 복사합니다.



3. GCP 콘솔장에서 인스턴스 이름을 클릭하고, ssh키 섹션에 [표시 및 수정]을 클릭하여 ssh 정보를 붙여넣기 합니다.



코멘트 옵션으로 이메알주소를 공개키에 추가했다면, 계정명이 자동으로 resolve 됩니다.



4. secureCRT 에서 GCP 인스턴스의 외부IP를 호스트명으로 설정하고, Public Key properties 에서 비밀키 경로를 설정하여 접속합니다.





은전한닢 형태소 분석기와 full-text-search 모듈 연동

POSTGRESQL 2018. 12. 10. 17:42
현재 사용중인 검색엔진의 캐싱 문제로 full-text-search에 대해 조사하던 중 
한글 형태소 분석기와 사전을 RDBMS로 활용할 수 있으면 효율적인 운영이 가능하겠다 싶어 
은전한닢 형태소 분석기를 postgreSQL Full text search 모듈에 연동해 보았습니다. 
external C 소스 및 컴파일 스크립트는 textsearch_ko 프로젝트를 참조하였습니다. 

 1. MECAB-KO 설치
$ yum install gcc-c++ libstdc++ -y
$ git clone https://bitbucket.org/eunjeon/mecab-ko.git
$ cd mecab-ko
$ ./configure 
$ make all && make install
2. MECAB-KO-DIC 설치
$ wget https://bitbucket.org/eunjeon/mecab-ko-dic/downloads/mecab-ko-dic-2.1.1-20180720.tar.gz
$ tar xzf mecab-ko-dic-2.1.1-20180720.tar.gz
$ cd mecab-ko-dic-2.1.1-20180720
$ ./configure
$ make all && make install

3. 형태소 분석기 설치확인

$ echo '아버지가방에들어가신다'|mecab
아버지  NNG,*,F,아버지,*,*,*,*
가      JKS,*,F,가,*,*,*,*
방      NNG,장소,T,방,*,*,*,*
에      JKB,*,F,에,*,*,*,*
들어가  VV,*,F,들어가,*,*,*,*
신다    EP+EC,*,F,신다,Inflect,EP,EC,시/EP/*+ㄴ다/EC/*
EOS
4. extension 소스를 github 에서 다운로드 후 컴파일
$ git clone https://github.com/i0seph/textsearch_ko.git
$ cd textsearch_ko
$ make USE_PGXS=1
$ make USE_PGXS=1 install
5. 함수생성 스크립트를 실행
postgres=# \i ts_mecab_ko.sql
SET
BEGIN
psql:ts_mecab_ko.sql:12: ERROR:  could not load library "/usr/local/pgsql/lib/ts_mecab_ko.so": libmecab.so.2: cannot open shared object file: No such file or directory
psql:ts_mecab_ko.sql:17: ERROR:  current transaction is aborted, commands ignored until end of transaction block
psql:ts_mecab_ko.sql:22: ERROR:  current transaction is aborted, commands ignored until end of transaction block
psql:ts_mecab_ko.sql:30: ERROR:  current transaction is aborted, commands ignored until end of transaction block
psql:ts_mecab_ko.sql:32: ERROR:  current transaction is aborted, commands ignored until end of transaction block
psql:ts_mecab_ko.sql:41: ERROR:  current transaction is aborted, commands ignored until end of transaction block
psql:ts_mecab_ko.sql:45: ERROR:  current transaction is aborted, commands ignored until end of transaction block
psql:ts_mecab_ko.sql:49: ERROR:  current transaction is aborted, commands ignored until end of transaction block
psql:ts_mecab_ko.sql:55: ERROR:  current transaction is aborted, commands ignored until end of transaction block
psql:ts_mecab_ko.sql:57: ERROR:  current transaction is aborted, commands ignored until end of transaction block
psql:ts_mecab_ko.sql:63: ERROR:  current transaction is aborted, commands ignored until end of transaction block
psql:ts_mecab_ko.sql:69: ERROR:  current transaction is aborted, commands ignored until end of transaction block
psql:ts_mecab_ko.sql:73: ERROR:  current transaction is aborted, commands ignored until end of transaction block
psql:ts_mecab_ko.sql:93: ERROR:  current transaction is aborted, commands ignored until end of transaction block
psql:ts_mecab_ko.sql:98: ERROR:  current transaction is aborted, commands ignored until end of transaction block
psql:ts_mecab_ko.sql:103: ERROR:  current transaction is aborted, commands ignored until end of transaction block
ROLLBACK
# libmecab.so.2 not found 오류 시 해당경로를 ld.so.conf 파일에 추가하여 리로드 합니다.
$ ldconfig -p|grep mecab
$ sudo find / -name libmecab.so.2
/usr/local/lib/libmecab.so.2
/home/postgres/soft/mecab-ko/src/.libs/libmecab.so.2
$ sudo vi /etc/ld.so.conf
include ld.so.conf.d/*.conf
/usr/local/lib # 추가
$ldconfig # /etc/ld.so.conf 리로드
$ ldconfig -p|grep mecab
        libmecab.so.2 (libc6,x86-64) => /usr/local/lib/libmecab.so.2
        libmecab.so (libc6,x86-64) => /usr/local/lib/libmecab.so
# 함수생성 스크립트를 실행합니다.
postgres=# \i ts_mecab_ko.sql 
SET
BEGIN
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE TEXT SEARCH PARSER
COMMENT
CREATE FUNCTION
CREATE TEXT SEARCH TEMPLATE
CREATE TEXT SEARCH DICTIONARY
CREATE TEXT SEARCH CONFIGURATION
COMMENT
ALTER TEXT SEARCH CONFIGURATION
ALTER TEXT SEARCH CONFIGURATION
ALTER TEXT SEARCH CONFIGURATION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
COMMIT
6. 함수생성 결과 확인
postgres=# select * from mecabko_analyze('아버지가방에들어가신다.');
  word  | type | part1st | partlast | pronounce | conjtype | conjugation | basic | detail | lucene 
--------+------+---------+----------+-----------+----------+-------------+-------+--------+--------
 아버지 | NNG  |         | F        | 아버지    |          |             |       |        | 아버지
 가     | JKS  |         | F        | 가        |          |             |       |        | 가
 방     | NNG  | 장소    | T        | 방        |          |             |       |        | 방
 에     | JKB  |         | F        | 에        |          |             |       |        | 에
 들어가 | VV   |         | F        | 들어가    |          |             |       |        | 들어가
 시     | EP   |         | F        | 시        |          |             |       |        | 
 ㄴ다     | EF   |         | F        | ㄴ다        |          |             |       |        | 
 .      | SF   |         |          | .         |          |             |       |        | .
(8 rows)
# 한국어 full-text-search 기능을 온전히 사용하기 위해 default_text_search_config 파라메터를 korean 으로 변경합니다.
postgres=# select * from to_tsvector('아버지가방에들어가신다');
        to_tsvector         
----------------------------
 '아버지가방에들어가신다':1
(1 row)

postgres=# show default_text_search_config;
 default_text_search_config 
----------------------------
 pg_catalog.english
(1 row)

postgres=# set default_text_search_config = 'korean';
SET
postgres=# select * from to_tsvector('아버지가방에들어가신다');
         to_tsvector          
------------------------------
 '들어가':3 '방':2 '아버지':1
(1 row)


이후 세부적인 테스트결과도 정리해 보려 합니다.


vacuum / vacuum full 의 차이

POSTGRESQL 2018. 12. 5. 18:20
postgresql 은 update 시 (고유의 MVCC매커니즘에서 기인하여) delete + insert 방식으로 처리하며, 
update를 포함 delete 실행결과 더이상 필요하지 않는 데이터 공간이 재활용되는 시점은 
적절한 scale factor로 구성된 vacuum 프로세스가 수행된 이후입니다. 
vacuum full을 수행했을 경우 새로운 relfilenode명과 함께 파일을 새로 생성하여 디스크 공간을 회수하지만, 
신규 파일쓰기가 완료될때까지 기존 데이터파일은 유지되므로 해당사이즈만큼 디스크 공간을 확보해야 하며, 
Exclusive Lock 이 발생하므로 주의해야 합니다. 

 아래는 pg_filedump 를 활용한 테스트 결과 입니다. 

 1. 소스 다운로드 : https://wiki.postgresql.org/wiki/Pg_filedump 

 2. make 명령으로 소스를 컴파일하여 설치합니다.
make
make install
3. 설치결과를 확인합니다.
localhost.localdomain:[/home/data/PG_DATA]pg_filedump -h

Version 11.0 (for PostgreSQL 11.x)
Copyright (c) 2002-2010 Red Hat, Inc.
Copyright (c) 2011-2018, PostgreSQL Global Development Group

Usage: pg_filedump [-abcdfhikxy] [-R startblock [endblock]] [-D attrlist] [-S blocksize] [-s segsize] [-n segnumber] file

Display formatted contents of a PostgreSQL heap/index/control file
Defaults are: relative addressing, range of the entire file, block
               size as listed on block 0 in the file

The following options are valid for heap and index files:
  -a  Display absolute addresses when formatting (Block header
      information is always block relative)
  -b  Display binary block images within a range (Option will turn
      off all formatting options)
  -d  Display formatted block content dump (Option will turn off
      all other formatting options)
  -D  Decode tuples using given comma separated list of types
      Supported types:
        bigint bigserial bool char charN date float float4 float8 int
        json macaddr name oid real serial smallint smallserial text
        time timestamp timetz uuid varchar varcharN xid xml
      ~ ignores all attributes left in a tuple
  -f  Display formatted block content dump along with interpretation
  -h  Display this information
  -i  Display interpreted item details
  -k  Verify block checksums
  -o  Do not dump old values.
  -R  Display specific block ranges within the file (Blocks are
      indexed from 0)
        [startblock]: block to start at
        [endblock]: block to end at
      A startblock without an endblock will format the single block
  -s  Force segment size to [segsize]
  -t  Dump TOAST files
  -v  Ouput additional information about TOAST relations
  -n  Force segment number to [segnumber]
  -S  Force block size to [blocksize]
  -x  Force interpreted formatting of block items as index items
  -y  Force interpreted formatting of block items as heap items

The following options are valid for control files:
  -c  Interpret the file listed as a control file
  -f  Display formatted content dump along with interpretation
  -S  Force block size to [blocksize]

Report bugs to 

4. 테스트를 위해 테이블 및 데이터를 생성합니다.

postgres=# create table tb_vacuum_test(c1 int, c2 varchar);                                                    
CREATE TABLE
postgres=# insert into tb_vacuum_test select n as c1, md5(n::varchar) as c2 from generate_series(1,10) as s(n);
INSERT 0 10
postgres=# select * from pg_relation_filenode('tb_vacuum_test');
 pg_relation_filenode 
----------------------
                49178
(1 row)

5. checkpoint 를 수행하지 않을경우 신규생성된 데이터가 buffer cache에만 존재하고 파일에 flush 되지 않았기 때문에 즉각적인 확인이 불가합니다.
localhost.localdomain:[/home/data/PG_DATA]find . -name 49178 -exec pg_filedump -D int,varchar {} \;
*******************************************************************
* PostgreSQL File/Block Formatted Dump Utility - Version 11.0
*
* File: ./base/13284/49178
* Options used: -D int,varchar 
*
* Dump created on: Wed Dec  5 08:31:20 2018
*******************************************************************
Error: Premature end of file encountered.

6. checkpoint 실행하여 buffer cache 영역의 데이터를 데이터파일로 flush 합니다.
postgres=# checkpoint;
CHECKPOINT
postgres=#
7. 덤프파일을 확인합니다.
localhost.localdomain:[/home/data/PG_DATA]find . -name 49178 -exec pg_filedump -D int,varchar {} \;

*******************************************************************
* PostgreSQL File/Block Formatted Dump Utility - Version 11.0
*
* File: ./base/13284/49178
* Options used: -D int,varchar 
*
* Dump created on: Wed Dec  5 08:31:44 2018
*******************************************************************

Block    0 ********************************************************
----- Block Offset: 0x00000000 Offsets: Lower 64 (0x0040) Block: Size 8192 Version 4 Upper 7552 (0x1d80) LSN: logid 1 recoff 0x8cc5d930 Special 8192 (0x2000) Items: 10 Free Space: 7488 Checksum: 0x0000 Prune XID: 0x00000000 Flags: 0x0000 () Length (including item array): 64 ------ Item 1 -- Length: 61 Offset: 8128 (0x1fc0) Flags: NORMAL COPY: 1 c4ca4238a0b923820dcc509a6f75849b Item 2 -- Length: 61 Offset: 8064 (0x1f80) Flags: NORMAL COPY: 2 c81e728d9d4c2f636f067f89cc14862c Item 3 -- Length: 61 Offset: 8000 (0x1f40) Flags: NORMAL COPY: 3 eccbc87e4b5ce2fe28308fd9f2a7baf3 Item 4 -- Length: 61 Offset: 7936 (0x1f00) Flags: NORMAL COPY: 4 a87ff679a2f3e71d9181a67b7542122c Item 5 -- Length: 61 Offset: 7872 (0x1ec0) Flags: NORMAL COPY: 5 e4da3b7fbbce2345d7772b0674a318d5 Item 6 -- Length: 61 Offset: 7808 (0x1e80) Flags: NORMAL COPY: 6 1679091c5a880faf6fb5e6087eb1b2dc Item 7 -- Length: 61 Offset: 7744 (0x1e40) Flags: NORMAL COPY: 7 8f14e45fceea167a5a36dedd4bea2543 Item 8 -- Length: 61 Offset: 7680 (0x1e00) Flags: NORMAL COPY: 8 c9f0f895fb98ab9159f51fd0297e236d Item 9 -- Length: 61 Offset: 7616 (0x1dc0) Flags: NORMAL COPY: 9 45c48cce2e2d7fbdea1afc51c7c6ad26 Item 10 -- Length: 61 Offset: 7552 (0x1d80) Flags: NORMAL COPY: 10 d3d9446802a44259755d38e6d163e820 *** End of File Encountered. Last Block Read: 0 ***
8. 실제 update 구문이 delete/insert 순서로 처리되는지 확인해 봅니다.
postgres=# update tb_vacuum_test set c2='a' where c1=1;
UPDATE 1
postgres=# checkpoint;
CHECKPOINT
postgres=#
9. 덤프파일 상 update 대상 row는 그대로 있고 신규 ROW 추가되어 있는 것을 확인할 수 있습니다.
localhost.localdomain:[/home/data/PG_DATA]find . -name 49178 -exec pg_filedump -D int,varchar {} \;

*******************************************************************
* PostgreSQL File/Block Formatted Dump Utility - Version 11.0
*
* File: ./base/13284/49178
* Options used: -D int,varchar 
*
* Dump created on: Wed Dec  5 08:34:15 2018
*******************************************************************

Block    0 ********************************************************
----- Block Offset: 0x00000000 Offsets: Lower 68 (0x0044) Block: Size 8192 Version 4 Upper 7520 (0x1d60) LSN: logid 1 recoff 0x8cc5dd98 Special 8192 (0x2000) Items: 11 Free Space: 7452 Checksum: 0x0000 Prune XID: 0x00000290 Flags: 0x0000 () Length (including item array): 68 ------ Item 1 -- Length: 61 Offset: 8128 (0x1fc0) Flags: NORMAL COPY: 1 c4ca4238a0b923820dcc509a6f75849b Item 2 -- Length: 61 Offset: 8064 (0x1f80) Flags: NORMAL COPY: 2 c81e728d9d4c2f636f067f89cc14862c Item 3 -- Length: 61 Offset: 8000 (0x1f40) Flags: NORMAL COPY: 3 eccbc87e4b5ce2fe28308fd9f2a7baf3 Item 4 -- Length: 61 Offset: 7936 (0x1f00) Flags: NORMAL COPY: 4 a87ff679a2f3e71d9181a67b7542122c Item 5 -- Length: 61 Offset: 7872 (0x1ec0) Flags: NORMAL COPY: 5 e4da3b7fbbce2345d7772b0674a318d5 Item 6 -- Length: 61 Offset: 7808 (0x1e80) Flags: NORMAL COPY: 6 1679091c5a880faf6fb5e6087eb1b2dc Item 7 -- Length: 61 Offset: 7744 (0x1e40) Flags: NORMAL COPY: 7 8f14e45fceea167a5a36dedd4bea2543 Item 8 -- Length: 61 Offset: 7680 (0x1e00) Flags: NORMAL COPY: 8 c9f0f895fb98ab9159f51fd0297e236d Item 9 -- Length: 61 Offset: 7616 (0x1dc0) Flags: NORMAL COPY: 9 45c48cce2e2d7fbdea1afc51c7c6ad26 Item 10 -- Length: 61 Offset: 7552 (0x1d80) Flags: NORMAL COPY: 10 d3d9446802a44259755d38e6d163e820 Item 11 -- Length: 30 Offset: 7520 (0x1d60) Flags: NORMAL COPY: 1 a
10. vacuum 실행 시 어떤 변화가 있는지 확인해 봅니다.
postgres=# vacuum tb_vacuum_test ;
VACUUM
postgres=# checkpoint;
CHECKPOINT
postgres=#
11. 덤프파일 확인결과 update 대상 ROW 플래그가 REDIRECT로 변경되어 있는것을 확인할 수 있습니다.
localhost.localdomain:[/home/data/PG_DATA]find . -name 49178 -exec pg_filedump -D int,varchar {} \;

*******************************************************************
* PostgreSQL File/Block Formatted Dump Utility - Version 11.0
*
* File: ./base/13284/49178
* Options used: -D int,varchar 
*
* Dump created on: Wed Dec  5 08:35:12 2018
*******************************************************************

Block    0 ********************************************************
----- Block Offset: 0x00000000 Offsets: Lower 68 (0x0044) Block: Size 8192 Version 4 Upper 7584 (0x1da0) LSN: logid 1 recoff 0x8cc5e1d0 Special 8192 (0x2000) Items: 11 Free Space: 7516 Checksum: 0x0000 Prune XID: 0x00000000 Flags: 0x0004 (ALL_VISIBLE) Length (including item array): 68 ------ Item 1 -- Length: 0 Offset: 11 (0x000b) Flags: REDIRECT Item 2 -- Length: 61 Offset: 8128 (0x1fc0) Flags: NORMAL COPY: 2 c81e728d9d4c2f636f067f89cc14862c Item 3 -- Length: 61 Offset: 8064 (0x1f80) Flags: NORMAL COPY: 3 eccbc87e4b5ce2fe28308fd9f2a7baf3 Item 4 -- Length: 61 Offset: 8000 (0x1f40) Flags: NORMAL COPY: 4 a87ff679a2f3e71d9181a67b7542122c Item 5 -- Length: 61 Offset: 7936 (0x1f00) Flags: NORMAL COPY: 5 e4da3b7fbbce2345d7772b0674a318d5 Item 6 -- Length: 61 Offset: 7872 (0x1ec0) Flags: NORMAL COPY: 6 1679091c5a880faf6fb5e6087eb1b2dc Item 7 -- Length: 61 Offset: 7808 (0x1e80) Flags: NORMAL COPY: 7 8f14e45fceea167a5a36dedd4bea2543 Item 8 -- Length: 61 Offset: 7744 (0x1e40) Flags: NORMAL COPY: 8 c9f0f895fb98ab9159f51fd0297e236d Item 9 -- Length: 61 Offset: 7680 (0x1e00) Flags: NORMAL COPY: 9 45c48cce2e2d7fbdea1afc51c7c6ad26 Item 10 -- Length: 61 Offset: 7616 (0x1dc0) Flags: NORMAL COPY: 10 d3d9446802a44259755d38e6d163e820 Item 11 -- Length: 30 Offset: 7584 (0x1da0) Flags: NORMAL COPY: 1 a *** End of File Encountered. Last Block Read: 0 ***
12. delete로 유효하지 않는 데이터공간을 재현하고 insert로 해당 공간을 재활용하는지 확인해 봅니다.
postgres=# delete from tb_vacuum_test where c1=2;
DELETE 1
postgres=# insert into tb_vacuum_test values(11,md5('11'));
INSERT 0 1
postgres=# checkpoint;
CHECKPOINT
postgres=#

13. 기존 삭제된 ROW 는 그대로 남아있고 신규생성된 ROW가 append 된것을 확인할 수 있습니다.
localhost.localdomain:[/home/data/PG_DATA]find . -name 49178 -exec pg_filedump -D int,varchar {} \;

*******************************************************************
* PostgreSQL File/Block Formatted Dump Utility - Version 11.0
*
* File: ./base/13284/49178
* Options used: -D int,varchar 
*
* Dump created on: Wed Dec  5 08:37:43 2018
*******************************************************************

Block    0 ********************************************************
----- Block Offset: 0x00000000 Offsets: Lower 72 (0x0048) Block: Size 8192 Version 4 Upper 7520 (0x1d60) LSN: logid 1 recoff 0x8cc61d20 Special 8192 (0x2000) Items: 12 Free Space: 7448 Checksum: 0x0000 Prune XID: 0x00000291 Flags: 0x0000 () Length (including item array): 72 ------ Item 1 -- Length: 0 Offset: 11 (0x000b) Flags: REDIRECT Item 2 -- Length: 61 Offset: 8128 (0x1fc0) Flags: NORMAL COPY: 2 c81e728d9d4c2f636f067f89cc14862c Item 3 -- Length: 61 Offset: 8064 (0x1f80) Flags: NORMAL COPY: 3 eccbc87e4b5ce2fe28308fd9f2a7baf3 Item 4 -- Length: 61 Offset: 8000 (0x1f40) Flags: NORMAL COPY: 4 a87ff679a2f3e71d9181a67b7542122c Item 5 -- Length: 61 Offset: 7936 (0x1f00) Flags: NORMAL COPY: 5 e4da3b7fbbce2345d7772b0674a318d5 Item 6 -- Length: 61 Offset: 7872 (0x1ec0) Flags: NORMAL COPY: 6 1679091c5a880faf6fb5e6087eb1b2dc Item 7 -- Length: 61 Offset: 7808 (0x1e80) Flags: NORMAL COPY: 7 8f14e45fceea167a5a36dedd4bea2543 Item 8 -- Length: 61 Offset: 7744 (0x1e40) Flags: NORMAL COPY: 8 c9f0f895fb98ab9159f51fd0297e236d Item 9 -- Length: 61 Offset: 7680 (0x1e00) Flags: NORMAL COPY: 9 45c48cce2e2d7fbdea1afc51c7c6ad26 Item 10 -- Length: 61 Offset: 7616 (0x1dc0) Flags: NORMAL COPY: 10 d3d9446802a44259755d38e6d163e820 Item 11 -- Length: 30 Offset: 7584 (0x1da0) Flags: NORMAL COPY: 1 a Item 12 -- Length: 61 Offset: 7520 (0x1d60) Flags: NORMAL COPY: 11 6512bd43d9caa6e02c990b0a82652dca *** End of File Encountered. Last Block Read: 0 ***
14. vacuum 수행 후 insert 처리결과를 확인합니다.
postgres=# vacuum tb_vacuum_test ;
VACUUM
postgres=# insert into tb_vacuum_test values(12,md5('12'));
INSERT 0 1
postgres=# checkpoint;
CHECKPOINT
postgres=#
15. 삭제된 ROW 공간을 신규ROW 공간으로 재활용했음을 확인할 수 있습니다.
localhost.localdomain:[/home/data/PG_DATA]find . -name 49178 -exec pg_filedump -D int,varchar {} \;

*******************************************************************
* PostgreSQL File/Block Formatted Dump Utility - Version 11.0
*
* File: ./base/13284/49178
* Options used: -D int,varchar 
*
* Dump created on: Wed Dec  5 08:39:10 2018
*******************************************************************

Block    0 ********************************************************
----- Block Offset: 0x00000000 Offsets: Lower 72 (0x0048) Block: Size 8192 Version 4 Upper 7520 (0x1d60) LSN: logid 1 recoff 0x8cc64288 Special 8192 (0x2000) Items: 12 Free Space: 7448 Checksum: 0x0000 Prune XID: 0x00000000 Flags: 0x0001 (HAS_FREE_LINES) Length (including item array): 72 ------ Item 1 -- Length: 0 Offset: 11 (0x000b) Flags: REDIRECT Item 2 -- Length: 61 Offset: 7520 (0x1d60) Flags: NORMAL COPY: 12 c20ad4d76fe97759aa27a0c99bff6710 Item 3 -- Length: 61 Offset: 8128 (0x1fc0) Flags: NORMAL COPY: 3 eccbc87e4b5ce2fe28308fd9f2a7baf3 Item 4 -- Length: 61 Offset: 8064 (0x1f80) Flags: NORMAL COPY: 4 a87ff679a2f3e71d9181a67b7542122c Item 5 -- Length: 61 Offset: 8000 (0x1f40) Flags: NORMAL COPY: 5 e4da3b7fbbce2345d7772b0674a318d5 Item 6 -- Length: 61 Offset: 7936 (0x1f00) Flags: NORMAL COPY: 6 1679091c5a880faf6fb5e6087eb1b2dc Item 7 -- Length: 61 Offset: 7872 (0x1ec0) Flags: NORMAL COPY: 7 8f14e45fceea167a5a36dedd4bea2543 Item 8 -- Length: 61 Offset: 7808 (0x1e80) Flags: NORMAL COPY: 8 c9f0f895fb98ab9159f51fd0297e236d Item 9 -- Length: 61 Offset: 7744 (0x1e40) Flags: NORMAL COPY: 9 45c48cce2e2d7fbdea1afc51c7c6ad26 Item 10 -- Length: 61 Offset: 7680 (0x1e00) Flags: NORMAL COPY: 10 d3d9446802a44259755d38e6d163e820 Item 11 -- Length: 30 Offset: 7648 (0x1de0) Flags: NORMAL COPY: 1 a Item 12 -- Length: 61 Offset: 7584 (0x1da0) Flags: NORMAL COPY: 11 6512bd43d9caa6e02c990b0a82652dca *** End of File Encountered. Last Block Read: 0 ***
16. vacuum 이 아닌 vacuum full 을 수행해 봅니다.
postgres=# vacuum full tb_vacuum_test ;
VACUUM
postgres=#
17. vacuum full 수행결과 신규파일이 생성된 후 이전파일은 삭제되므로 더 이상 데이터가 존재하지 않습니다.
localhost.localdomain:[/home/data/PG_DATA]find . -name 49178 -exec pg_filedump -D int,varchar {} \;

*******************************************************************
* PostgreSQL File/Block Formatted Dump Utility - Version 11.0
*
* File: ./base/13284/49178
* Options used: -D int,varchar 
*
* Dump created on: Wed Dec  5 08:41:22 2018
*******************************************************************
Error: Unable to read full page header from block 0.
  ===> Read 0 bytes
Error: Premature end of file encountered.
18. 새로 생성된 relfilenode를 확인합니다.
postgres=# select * from pg_relation_filenode('tb_vacuum_test');
 pg_relation_filenode 
----------------------
                49184
(1 row)
19. 신규생성된 데이터파일로부터 필요하지 않는 디스크 공간은 모두 회수되었음을 확인할 수 있습니다.
localhost.localdomain:[/home/data/PG_DATA]find . -name 49184 -exec pg_filedump -D int,varchar {} \;  

*******************************************************************
* PostgreSQL File/Block Formatted Dump Utility - Version 11.0
*
* File: ./base/13284/49184
* Options used: -D int,varchar 
*
* Dump created on: Wed Dec  5 08:43:03 2018
*******************************************************************

Block    0 ********************************************************
----- Block Offset: 0x00000000 Offsets: Lower 68 (0x0044) Block: Size 8192 Version 4 Upper 7520 (0x1d60) LSN: logid 1 recoff 0x8cc7e720 Special 8192 (0x2000) Items: 11 Free Space: 7452 Checksum: 0x0000 Prune XID: 0x00000000 Flags: 0x0000 () Length (including item array): 68 ------ Item 1 -- Length: 61 Offset: 8128 (0x1fc0) Flags: NORMAL COPY: 12 c20ad4d76fe97759aa27a0c99bff6710 Item 2 -- Length: 61 Offset: 8064 (0x1f80) Flags: NORMAL COPY: 3 eccbc87e4b5ce2fe28308fd9f2a7baf3 Item 3 -- Length: 61 Offset: 8000 (0x1f40) Flags: NORMAL COPY: 4 a87ff679a2f3e71d9181a67b7542122c Item 4 -- Length: 61 Offset: 7936 (0x1f00) Flags: NORMAL COPY: 5 e4da3b7fbbce2345d7772b0674a318d5 Item 5 -- Length: 61 Offset: 7872 (0x1ec0) Flags: NORMAL COPY: 6 1679091c5a880faf6fb5e6087eb1b2dc Item 6 -- Length: 61 Offset: 7808 (0x1e80) Flags: NORMAL COPY: 7 8f14e45fceea167a5a36dedd4bea2543 Item 7 -- Length: 61 Offset: 7744 (0x1e40) Flags: NORMAL COPY: 8 c9f0f895fb98ab9159f51fd0297e236d Item 8 -- Length: 61 Offset: 7680 (0x1e00) Flags: NORMAL COPY: 9 45c48cce2e2d7fbdea1afc51c7c6ad26 Item 9 -- Length: 61 Offset: 7616 (0x1dc0) Flags: NORMAL COPY: 10 d3d9446802a44259755d38e6d163e820 Item 10 -- Length: 30 Offset: 7584 (0x1da0) Flags: NORMAL COPY: 1 a Item 11 -- Length: 61 Offset: 7520 (0x1d60) Flags: NORMAL COPY: 11 6512bd43d9caa6e02c990b0a82652dca *** End of File Encountered. Last Block Read: 0 ***


gdb를 통한 postgresql 소스 디버깅

POSTGRESQL 2018. 12. 5. 14:21

1. gdb 를 설치합니다.

yum install gdb
2. postgresql 설치 전 configure 옵션으로 옵티마이져 모드를 비활성화 하는것이 좋습니다.
./configure --enable-cassert --enable-debug CFLAGS="-ggdb -O0"
3. 소스를 이용하여 컴파일 설치가 완료되고 나면 
backend process pid를 확인하여 gdb attach 구문으로 디버깅을 수행할 수 있습니다. 

# 디버깅하려하는 프로세스 아이디 확인
test=# select pg_backend_pid();
 pg_backend_pid 
----------------
           3167
(1 row)
# pid를 attach 하여 디버깅 실행

$ gdb -p 3167 GNU gdb (GDB) Red Hat Enterprise Linux (7.2-60.el6_4.1) Copyright (C) 2010 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later

This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-redhat-linux-gnu". For bug reporting instructions, please see: . Attaching to process 3193 Reading symbols from /usr/local/pgsql93/bin/postgres...done. Reading symbols from /usr/lib64/libssl.so.10...(no debugging symbols found)...done. Loaded symbols for /usr/lib64/libssl.so.10 Reading symbols from /usr/lib64/libcrypto.so.10...(no debugging symbols found)...done. Loaded symbols for /usr/lib64/libcrypto.so.10 Reading symbols from /lib64/libdl.so.2...(no debugging symbols found)...done. Loaded symbols for /lib64/libdl.so.2 Reading symbols from /lib64/libm.so.6...(no debugging symbols found)...done. Loaded symbols for /lib64/libm.so.6 Reading symbols from /lib64/libc.so.6...(no debugging symbols found)...done. Loaded symbols for /lib64/libc.so.6 Reading symbols from /lib64/libgssapi_krb5.so.2...(no debugging symbols found)...done. Loaded symbols for /lib64/libgssapi_krb5.so.2 Reading symbols from /lib64/libkrb5.so.3...(no debugging symbols found)...done. Loaded symbols for /lib64/libkrb5.so.3 Reading symbols from /lib64/libcom_err.so.2...(no debugging symbols found)...done. Loaded symbols for /lib64/libcom_err.so.2 Reading symbols from /lib64/libk5crypto.so.3...(no debugging symbols found)...done. Loaded symbols for /lib64/libk5crypto.so.3 Reading symbols from /lib64/libz.so.1...(no debugging symbols found)...done. Loaded symbols for /lib64/libz.so.1 Reading symbols from /lib64/ld-linux-x86-64.so.2...(no debugging symbols found)...done. Loaded symbols for /lib64/ld-linux-x86-64.so.2 Reading symbols from /lib64/libkrb5support.so.0...(no debugging symbols found)...done. Loaded symbols for /lib64/libkrb5support.so.0 Reading symbols from /lib64/libkeyutils.so.1...(no debugging symbols found)...done. Loaded symbols for /lib64/libkeyutils.so.1 Reading symbols from /lib64/libresolv.so.2...(no debugging symbols found)...done. Loaded symbols for /lib64/libresolv.so.2 Reading symbols from /lib64/libpthread.so.0...(no debugging symbols found)...done. [Thread debugging using libthread_db enabled] Loaded symbols for /lib64/libpthread.so.0 Reading symbols from /lib64/libselinux.so.1...(no debugging symbols found)...done. Loaded symbols for /lib64/libselinux.so.1 Reading symbols from /lib64/libnss_files.so.2...(no debugging symbols found)...done. Loaded symbols for /lib64/libnss_files.so.2 0x0000003f002e98c2 in recv () from /lib64/libc.so.6 Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.132.el6_5.2.x86_64 keyutils-libs-1.4-4.el6.x86_64 krb5-libs-1.10.3-15.el6_5.1.x86_64 libcom_err-1.41.12-18.el6.x86_64 libselinux-2.0.94-5.3.el6_4.1.x86_64 openssl-1.0.1e-16.el6_5.14.x86_64 zlib-1.2.3-29.el6.x86_64 (gdb)
# breakpoint 지정
(gdb) b raw_parser
Breakpoint 1 at 0x57a733: file parser.c, line 42.
(gdb) 
# 쿼리실행

test=# select 'this is test'; // gdb 컨트롤 대기

# 디버깅 컨트롤
 (gdb) continue

Continuing.

 

Breakpoint 1, raw_parser (str=0x253dd38 "select 'this is test';") at parser.c:42

42              yyscanner = scanner_init(str, &yyextra.core_yy_extra,

(gdb) 




replication 자동구성 스크립트(테스트 only)

POSTGRESQL 2018. 12. 4. 14:35

local 테스트 환경에서 replication 구성 스크립트 입니다.

#!/bin/sh

set -e

export PG_BIN_DIR="/usr/local/pgsql/bin"
export PG_DIR="$PWD"

export PRIMARY_PORT=5444
export REPLICA_PORT=5445

read -p "Will delete $PG_DIR/data/{primary,replica}. Okay? [Ctrl+C cancels]" yn
rm -rf $PG_DIR/data/primary
rm -rf $PG_DIR/data/replica

# Initialize a new data directory for the primary, then use a bit of a shortcut
# by just copying it for use by the replica.
$PG_BIN_DIR/initdb -D $PG_DIR/data/primary/
cp -r $PG_DIR/data/primary/ $PG_DIR/data/replica/

cat << EOT >> $PG_DIR/data/primary/postgresql.conf
port=$PRIMARY_PORT
EOT

cat << EOT >> $PG_DIR/data/replica/postgresql.conf
port=$REPLICA_PORT
shared_buffers=500MB
hot_standby=on
hot_standby_feedback=on
EOT

cat << EOT >> $PG_DIR/data/replica/recovery.conf
standby_mode=on
primary_conninfo='host=127.0.0.1 port=$PRIMARY_PORT user=$USER'
EOT

cat << EOT >> /dev/stdout
READY!
======

Start primary:
    $PG_BIN_DIR/postgres -D $PG_DIR/data/primary

Start replica:
    $PG_BIN_DIR/postgres -D $PG_DIR/data/replica

Create a database:
    $PG_BIN_DIR/createdb -p $PRIMARY_PORT mydb

Connect to primary:
    $PG_BIN_DIR/psql -p $PRIMARY_PORT mydb

Connect to replica:
    $PG_BIN_DIR/psql -p $REPLICA_PORT mydb
EOT

신규컬럼 추가 시 default value에 의한 lock 발생현상 및 회피요령

POSTGRESQL 2018. 12. 3. 10:11

신규컬럼 추가 시 default value와 함께 DDL 구문을 수행할 경우 exclusive lock을 점유한 채 테이블 내 모든 컬럼을 rewrite 하므로 후속트랜젝션은 해당테이블 읽기/쓰기가 불가합니다.


# 예시를 위한 테이블 데이터 생성

create table tb_default_val_test as select x.id from generate_series(1,10000000) as x(id);


# default value구문으로 컬럼 추가

postgres=# select pg_backend_pid();
 pg_backend_pid 
----------------
           3232
(1 row)

postgres=# alter table tb_default_val_test drop column last_update;


# lock 확인

postgres=# select pid, mode from pg_locks 
            where relation = (select oid from pg_class where relname='tb_default_val_test');           
 pid  |        mode         
------+---------------------
 3232 | AccessExclusiveLock
(1 row)


피해갈 수 있는 가장 간단한 방법은 DDL 구문와 DML 구문으로 분리하여 실행하는 것입니다.

(온라인 상태에서 DDL 구문 실행TIP은 "여기"를 참조)

alter table tb_default_val_test add column last_update timestamptz;
ALTER TABLE

postgres=# update tb_default_val_test set last_update = now();


여기서 문제가 될 수 있는 또한가지는 아래처럼 테이블 전체 update 가 완료될때까지 DML 수행이 불가합니다.

postgres=# select pg_backend_pid();
 pg_backend_pid 
----------------
           3327
(1 row)

postgres=# update tb_default_val_test set last_update = now() where id = 1;


# lock 조회 결과

postgres=# select pid, mode from pg_locks 
            where relation = (select oid from pg_class where relname='tb_default_val_test') order by pid;
 pid  |       mode       
------+------------------
 3232 | RowExclusiveLock
 3327 | RowExclusiveLock
 3327 | ExclusiveLock


이럴경우 배치로 후속트랜젝션에 대한 간섭을 최소화할 수 있습니다.

아래는 스크립트(Python / psycopg2) 예시 입니다.

updateStr = """ update TB_DEFAULT_VAL_TEST
                   set last_update = now()
                 where ID in (select ID from TB_DEFAULT_VAL_TEST
                               where last_update is null
                               limit 5000)"""
updatedRowCnt = 0
while(True):
        count = 1
        cur.execute(updateStr)
        print("{0} number of rows updated".format(str(cur.rowcount)))
        count += 1
        updatedRowCnt += cur.rowcount
        if cur.rowcount < 1:
                print("END : {0} number of rows updated".format(updatedRowCnt))
                break


DDL 구문으로 인한 테이블 읽기/쓰기 lock 발생현상과 회피요령

POSTGRESQL 2018. 11. 29. 16:23

postgreSQL 에서 모든 lock 요청이력은 큐로 관리되며, 순차적으로 처리됩니다.

예를들어 트랜젝션 A 가 특정테이블에 락을 확보한 상태에서 트랜젝션B가 상위수준의 LOCK 을 요청할 경우 A트랜젝션이 선점했던 락을 해제할때까지 대기하게 되는데 여기서 주의해야 할 점은 트랜젝션B에서 DDL을 실행할 경우 exclusive lock을 요청한 상태로 대기하게 되고  후속 트랜젝션C는 선행트렌젝션 B에 의한 LOCK이 해제될때 까지 읽기/쓰기가 제한됩니다.

아래는 예시입니다. ( 버젼 : 11.1 )

1. 시간 T1에 트랜잭션 A에서 SELECT를 수행

 postgres=# select pg_backend_pid();
 pg_backend_pid 
----------------
          26965
(1 row)

postgres=# begin; --long term SELECT / begin 구문으로 상황 연출
BEGIN
postgres=# select * from items;
 item_no | item_name | last_update 
---------+-----------+-------------
(0 rows)

postgres=# select pid,mode from pg_locks where relation = (select oid from pg_class where relname='items');
  pid  |      mode       
-------+-----------------
 26965 | AccessShareLock
(1 row)


2. 시간 T2에 트랜젝션 B에서 DDL 을 수행

postgres=# alter table items add column last_update timestamptz;

-- 대기

3. 트랜젝션B에 의한 AcessExclusiveLock 대기

postgres=# select pid, mode from pg_locks where relation = (select oid from pg_class where relname='items');

-------+---------------------  56617 | AccessExclusiveLock  26915 | AccessShareLock (2 rows)

4. 시간 T3에 트랜젝션C에서 SELECT 요청

postgres=# select * from items;-- 대기

5. 시간T1 에 트랜젝션A에서 수행했던 SELECT 문 처리 종료 시 트랜젝션B 와 C 가 순차적으로 처리

- 트랜잭션 A종료

postgres=# begin;
BEGIN
postgres=# select * from items;
 item_no | item_name 
---------+-----------
(0 rows)

postgres=# commit;
COMMIT
postgres=#

- 트랜잭션 B 종료

postgres=# alter table items add column last_update timestamptz;
ALTER TABLE
postgres=#

- 트랜젝션 C 종료

postgres=# select * from items;
---------+-----------+-------------
(0 rows)

postgres=#

결론적으로 선행 트랜젝션이 종료되지 않은 상태에서 access exclusive lock을 요청했을 경우 후행 트랜젝션 처리가 불가하며, 해당 테이블에 대한 읽기/쓰기가 제한되는 상황이 발생할 수 있습니다.

이런 상황을 방지하기 위해 특정시간(혹은 즉시)동안 lock확보에 실패할 경우 대기큐에서 강제로 소멸시키는 방법을 사용합니다.

1. T1시간에 트랜젝션 A에서 shared lock 점유

postgres=# begin;
BEGIN
postgres=# select pg_backend_pid();

pg_backend_pid ---------------- 56617 (1 row) postgres=# select * from items;  item_no | item_name | last_update ---------+-----------+------------- (0 rows) postgres=# select pid, mode from pg_locks where relation = (select oid from pg_class where relname='items');   pid  |      mode        -------+-----------------  56617 | AccessShareLock (1 row) postgres=#
2. T2 시간에 트랜젝션 B에서 lock_timeout 을 1ms로 설정 후 DDL을 수행
postgres=# set lock_timeout to '1ms';
SET
postgres=# alter table items drop column last_update;
ERROR:  canceling statement due to lock timeout -- lock_timeout 으로 강제종료
postgres=#

필자의 경우 해당 테이블에 선행되는  lock 이 없을때까지 1초에 펀칭해 줍니다.


pgbadger 로그파일로부터 SQL 정보 수집하기

POSTGRESQL 2015. 6. 24. 15:19

pgbadger를 이용하여 로그파일로부터 SQL정보를 수집할 수 있습니다. postgresql.conf 파일상의 로깅관련 설정값을 변경해야 합니다..

 

1. pgbadger 다운로드합니다.

$ git clone https://github.com/dalibo/pgbadger.git

 

2. postgresql.conf 파일에서 로깅관련 파라메터 수정 후 reload 합니다.
log_min_duration_statement = 0 # 단위 : 1/1000초,  0 : 모든쿼리를 로깅
log_checkpoints = on
log_connections = on
log_disconnections = on
log_lock_waits = on
log_temp_files = 0
log_line_prefix = '%t [%p]: [%l-1] db=%d,user=%u '

 

다운받은 디렉토리에 있는 pgbadger를 실행할 수 있습니다.
 $ ./pgbadger logfile_name
 $ ls -lrt
 -rw-r--r-- 1 postgres dba 1030734 2014-10-03 02:20 out.html

JDBC 테스트 소스

POSTGRESQL 2015. 6. 24. 15:07

postgreSQL JDBC 접속테스트 소스입니다.

 

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;
 
public class JDBCExample {
 
 public static void main(String[] argv) {
  Connection conn = null;
  Statement  st   = null;
  ResultSet  rs   = null;

// connection 속성 설정
  String     url      = "jdbc:postgresql://localhost/postgres";
  String     user     = "postgres";
  String     password = "postgres";
  String    query    = "select version()";

  System.out.println("-------- PostgreSQL JDBC Connection Testing ------------");
  
  try {
// postgresql 드라이버 클래스를 JDBC드라이버 메니저에 로드
   Class.forName("org.postgresql.Driver");
 
  } catch (ClassNotFoundException e) {
 
   System.out.println("Where is your PostgreSQL JDBC Driver? "
     + "Include in your library path!");
   e.printStackTrace();
   return;
 
  }
 
  System.out.println("PostgreSQL JDBC Driver Registered!");
 
  //Connection conn = null;
 
  try {
// conncetion establish 하기
   conn = DriverManager.getConnection(url, user, password);
 
  } catch (SQLException e) {
 
   System.out.println("Connection Failed! Check output console");
   e.printStackTrace();
   return;
 
  }
 
  if (conn != null) {
   System.out.println("Connection established!");
  } else {
   System.out.println("Failed to make conn!");
  }

// 실행할 쿼리 statement 입력
  try { 
   st = conn.createStatement();
   rs = st.executeQuery(query);
   while (rs.next()) {
    String ver = rs.getString("version");
    System.out.println("Your version : " + ver);
   }
  } 
  catch (SQLException e ) {
   System.out.println("query execution failed...");
   e.printStackTrace();
                        return;
  } 
  
// statement / connection close 하기
  try {
   if (st   != null) { st.close(); }
   if (conn != null) {conn.close(); }
  }
  catch (SQLException e ) {
                        System.out.println("failed to close statement or connection");
                        e.printStackTrace();
                        return;
                }
 } //main end
}// JDBCExample class end