반응형

systemctl 명령어를 통한 시작 방식은

 

root로만 접근해야 하는 것이라

 

WAS(tomcat)의 기동 및 중지 권한을 넘겨주고

 

재부팅시에도 자동으로 시작될수 있도록(tomcat 계정)

 

할려면 다음과 같다.

 

1. 퍼미션 부여

chmod 755 /etc/rc.d/rc.local

 

2. rc.local 실행 명령어 추가

아래는 tomcat 계정으로 서비스를 시작한다는 명령어

nohup su - tomcat -c /home/tomcat/apache-tomcat-8.5.40/bin/startup.sh &

 

3. rc.local 서비스 등록

위와 같이 해도 rc.local이 시작시에 시작되려고 하려면

아래와 같이 rc-local 서비스가 시작상태여야 함 (한번만 시작하면 계속 시작되어 있음)

#> systemctl start rc-local

 

 

반응형
반응형

참고 사이트 : 
1. 설정값 들 정보
   https://www.lesstif.com/pages/viewpage.action?pageId=43843899
2. Centos 7 설치 관련
   https://idchowto.com/?p=43787

불러오는 중입니다...

 

Centos 7에 아무생각없이... (난 보안 담당자는 아님....)

 

서비스를 오픈하고 나니

 

불특정 다수에게 서비스를 제공해야 하는 상황이 발생하였습니다...

 

우리만 접근하면 참 좋은데

 

너무 많은 불법 접근 및 접속 요청이 있는 것을 알았고

 

이것을 능동적으로 대응하기 위해

 

fail2ban 패키지가 있다는 사실을 알게되어 글을 씁니다.

 

ssh, mariadb, http 불법 접근시 자동으로 ip ban이 되도록 하였습니다.

 



1. fail2ban 설치

 

다음 명령어를 통해 저장소를 설치할 수 있습니다.

 

# rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm 

# yum install -y fail2ban fail2ban-systemd 


redhat 계열에서 접근로그파일은 /var/log/secure 입니다.  

centos 7 이전에는 secure 로그파일에 의존하여 동작했으나 centos 7 부터는 systemd에서 통합적으로 관리합니다.

※ 이 설정과 관련하여 별도의 파일로 설정할 때는 2가지 옵션을 추가해야 합니다.

 ( backend = polling
   banaction = iptables-multiport )

로그파일이 없으면 fail2ban동작불가하기 때문에 fail2ban-systemd를 같이 설치진행합니다

 

centos 시작시 fail2ban이 자동으로 시작하도록 설정

# systemctl enable fail2ban  

fail2ban 서비스 시작

# systemctl start fail2ban 




2. 설정

fail2ban 의 기본 설정은 /etc/fail2ban/jail.conf 에 있지만 설정을 수정할 필요가 있을 경우 

기본 설정 파일은 놔두고 개인화 설정 파일인 /etc/fail2ban/jail.local 을 사용하는 것이 좋습니다.

설정 파일은 Windows 의 ini 파일처럼 [항목명] 밑에 key=value 형식으로 기술하면 되며 

# 은 주석이며 주요 설정은 [DEFAULT] 항목 밑에 기술하며 여러 기능중에 주요 설정 항목을 알아 봅시다.

 

2019.04.30일 기준으로 기본 설치하면 /etc/fail2ban/jail.conf 파일만 존재하며 jail.local은 보이지 않습니다.

                  복사하여 사용하는 방식으로 보이나 아래 설정들은 그냥 사용하였습니다. (jail.conf은 백업 받아둠)

                  



2.1 ignoreip

ignoreip 에 설정된 IP 는 로그인을 실패해도 차단하지 않으며 기본 설정은 localhost 입니다. 

IP를 추가할 경우 jail.local 파일 의 [DEFAULT] 항목에 기술해 주면 되며 여러개를 기술할 경우 

공백이나 , 로 구분해 주면 되며 아래는 로컬호스트와 192.168.10.x 대역은 차단하지 않는 설정입니다.

ignoreip = 127.0.0.1/8 192.168.10.0/24 



2.2 bantime 

bantime 은 지정된 조건(아래에서 설명할 findtime, maxretry)에 따라 인증에 실패한 클라이언트를 차단할 

시간을 초 단위로 지정하며 기본 설정은 600초(10분)입니다.


2.3 findtime, maxretry

findtime 과 maxretry 는 클라이언트를 차단하기 위한 조건을 지정하며 findtime 에 

지정한 시간(초 단위)내에 maxretry 에 설정한 횟수만큼 인증을 실패하면 차단하게 됩니다.

즉 위 설정은 600초(10분) 내에 5번을 인증 실패할 경우 bantime 만큼 차단합니다.

특정 IP에서 차단후에도 지속적으로 인증을 시도할 경우 bantime 을 점점 길게 해서 

시도 횟수를 줄이는 게 필요하지만 이 기능은 개발 버전(0.10.x)에만 반영되어 있고 0.9 버전에는 구현되어 있지 않습니다.

실제 운영 환경에서는 bantime 을 몇 시간 정도로 길게 주는 것이 보안상 유리합니다.



2.4 mail 알림

 

# 1 
destemail = sysadmin@example.com 
  
  
# 2 
sender = fail2ban@my-server.com 
  
# 3 
mta = sendmail 
  
# 4 
action = %(action_mw)s 


fail2ban 으로 차단한 정보를 메일로 전송할 경우 destemail 에 수신자 이메일 주소를  설정하고 sender 에는  "보낸 사람"의 정보를 설정합니다.

보낸 사람 이름은 전자 메일의 "보낸 사람"필드의 값을 설정하며 mta 매개 변수는 메일을 보내는 데 사용할 메일 서비스를 설정하며 기본은 sendmail 입니다.

중요한 부분은 action 항목으로 차단했을 때 실행할 액션을 의미하며 기본 설정은 아무 일도 안 하는 설정인 "action = %(action_)s" 입니다.

기본 설정으로는 차단 내용을 메일로 전송하지 않으므로 action_ 을 action_mw 나 action_mwl 로 지정해야 메일을 전송하며 차이는 아래와 같습니다.

action_mw : 메일을 전송하고 whois 로 IP 정보를 조회한 결과를 첨부
action_mwl : 메일을 전송하고 whois 로 IP 정보를 조회한 결과와 관련된 로그를 첨부


권장 설정은 다음과 같이 메일을 전송하고 whois 와 log 를 첨부하는 설정입니다.

action = %(action_mwl)s 

※ 위 설정으로 하는 경우 메일 송신시 해당 IP의 정보를 검색한 결과도 같이 송부됩니다.

 

   따라서 whois 명령을 통해 정보가 조회될 수 있어야 합니다.


whois 가 설치되지 않은 경우 

 

$ sudo yum install whois 를 실행합니다.


2.5 개별 jail 설정

기본적으로 fail2ban 은 차단하지 않으므로 차단할 서비스를 [sshd] 처럼 항목명에 서비스를 등록하고 enabled = true 를 추가해 주면 차단됩니다.

2.6 포트 번호 변경

ssh 를 다른 포트로 사용할 경우 port 항목에 해당 포트를 기술해 주면 됩니다. 만약 ssh 가 여러 포트를 사용한다면 

, 를 구분자로 해서 포트를 모두 기술하며 아래는 ssh 기본 포트와 10022 를 사용할 경우 설정입니다.

 

[sshd] 
enabled = true 
port = ssh,10022 
sshd jail 설정 


3. 최종 설정값

 이제 ssh 를 위한 최종 /etc/fail2ban/jail.local 파일을 완성해 보면 아래와 같습니다.

[DEFAULT] 
  
## 차단하지 않을 IP 
ignoreip = 127.0.0.1/8 192.168.10.0/24 
  
# 1시간 차단 (-1로 설정할 경우 영구 차단) 
bantime  = 3600 
  
# 아래 시간동안 maxretry 만큼 실패시 차단 
findtime  = 300 
  
# 최대 허용 횟수 
maxretry = 5 
  
# 메일 수신자, 다중 수신자는 지원 안 함 
destemail = sysadmin@example.com 
  
# 메일 보낸 사람 
sender = fail2ban@my-server.com 
  
# 메일 전송 프로그램 
mta = sendmail 
  
  
# 차단시 whois 정보와 관련 로그를 첨부하여 메일 전송 
action = %(action_mwl)s 
  
# sshd 서비스 차단 
[sshd] 
enabled = true 
port     = ssh, 10022 



4. 서비스 적용

설정을 마쳤으면 서비스를 재시작하여 변경 사항을 반영합니다. 

$ sudo systemctl restart fail2ban 


fail2ban 동작 로그는 /var/log/fail2ban.log 에 기록되므로 tail -f /var/log/fail2ban.log 명령어로 확인해 볼 수 있습니다.


5. firewall-cmd 커널 방화벽 설정 규칙 확인

이제 firewall-cmd 로 커널 방화벽 설정 규칙을 확인해 보겠습니다.

$ sudo firewall-cmd --direct --get-all-rules
ipv4 filter INPUT 0 -p tcp -m multiport --dports ssh -m set --match-set fail2ban-sshd src -j REJECT --reject-with icmp-port-unreachable
fail2ban이 추가한 firewall 설정 규칙
결과에 보이는 "--match-set fail2ban-sshd src" 는 ipset 그룹명이 fail2ban-sshd 인 source 에서 ssh 서비스로 전송한 패킷은 REJECT 하라는 의미입니다.


6. 차단 설정된 정보 조회

설정된 ipset 의 정보는 --list 옵션으로 확인할 수 있습니다.

 

$ sudo ipset --list    

  
Name: fail2ban-sshd 
Type: hash:ip 
Revision: 1 
Header: family inet hashsize 1024 maxelem 65536 timeout 10800 
Size in memory: 16592 
References: 1 
Members: 
192.168.58.1 timeout 89 

ipset 정책 목록
이제 fail2ban-sshd 규칙의 Header 항목에 있는 "timeout 3600"  에 따라 인증에 실패한 IP 는 1시간 동안 차단됩니다.


7. 전체 차단 정보 조회

이제 fail2ban-sshd 규칙의 Header 항목에 있는 "timeout 10800"  에 따라 인증에 실패한 IP 는 3시간 동안 차단됩니다.

전체 차단 정보는 fail2ban 의 클라이언트 유틸리티인 fail2ban-client 명령어를 통해서 확인해 볼 수 있습니다.

$ sudo fail2ban-client status sshd 

Status for the jail: sshd 
|- Filter 
|  |- Currently failed: 1 
|  |- Total failed:     8 
|  `- Journal matches:  _SYSTEMD_UNIT=sshd.service + _COMM=sshd 
`- Actions 
   |- Currently banned: 2 
   |- Total banned:     2 
   `- Banned IP list: 103.114.106.xxx 58.242.83.xxx 

 

8. mariadb 10 로그 파일 설정

8.1 로그 경로 생성

 

# mkdir /var/log/mariadb 

# chown mysql:mysql /var/log/mariadb 


8.2  로그 출력 경로 추가

/etc/my.cnf 파일에 다음 내역 추가

[mysqld] 항목 아래에

[mysqld]
log-error=/var/log/mariadb/mariadb.log 


8.3. mariadb 재시작

 

# systemctl restart mariadb 


9. fail2ban 에서 mariadb 설정

위의 8번 항목에 mairadb 설정 로그 출력까지 정상적으로 확인되면

다음 작업을 수행합니다.

/etc/fail2ban/jail.conf 파일

[mysqld-auth] 
# 설정 활성화 
enabled = true 

# 3306 포트 
port     = 3306 

# 로그 경로 
logpath  = /var/log/mariadb/mariadb.log 

# 최대 5회 실패시 적용 
maxretry = 5 

# 사용 필터 
filter   = mysqld-auth 
  
# 폴링 방식 적용 
backend = polling 

# ban 적용 
banaction = iptables-multiport 



9.1 재시작

위의 세팅 정보를 모두 설정하고

아래 명령을 통해 재시작 합니다.

 

# systemctl restart fail2ban 


10. apache tomcat 404 error 설정 추가 하기

10.1 tomcat 필터 파일 생성


404 에 대한 block 처리 방식입니다.

다만, 사전에 소스에 404 Not Found가 발생하는지 여부를 정확하게 확인이 필요합니다.
(아니면 모두 block 처리됨)

아래의 위치에 적용할 필터 명에 파일을 생성합니다.

- 위치 : /etc/fail2ban/filter.d
- 파일명 : tomcat.conf

[INCLUDES] 
before = common.conf 

[Definition] 
failregex =  ^.*\/.*\/.*404 
ignoreregex = 



10.2. jail.conf 정보 추가.

/etc/fail2ban/jail.conf 파일

[tomcat] 
# 설정 활성화 
enabled  = true 

# 적용 서비스 및 포트 
port     = http,https,80,443 

# block 설정 하지 않을 IP  대역대 
ignoreip = 127.0.0.1/8 192.168.10.0/24 

# ban (초) 시간 
bantime  = 3600 

# 최근 10분 동안 
findtime = 600 

# 설정 filter tomcat 
filter   = tomcat 

# 톰켓 로그 폴더 
logpath  = /var/log/tomcat8/localhost_access_log.*.txt 

# 재시도 회수 
maxretry = 5 

# 폴링 방식 적용 
backend = polling 

# ban 적용 
banaction = iptables-multiport 


10.3 재시작

위의 세팅 정보를 모두 설정하고

아래 명령을 통해 재시작 합니다.

# systemctl restart fail2ban 


11. php 파일 관련 설정 추가

11.1 필터 파일 생성

구축 사이트는 PHP 파일이 존재하지 않으므로

모든 php 파일의 404 not found의 경우에는 block 처리 함.


- 위치 : /etc/fail2ban/filter.d
- 파일명 : tomcat-php-404.conf

 

[INCLUDES] 
before = common.conf 

[Definition] 

failregex = ^ - - \[.*\] ".*\.php HTTP/1.1" 404 \d+$ 
            ^ - - \[.*\] ".*\.PHP HTTP/1.1" 404 \d+$ 

ignoreregex = 

 

11.2 필터 설정 적용

/etc/fail2ban/jail.conf 파일

[tomcat-php-404] 
# 설정 활성화 
enabled  = true 

# 적용 서비스 및 포트 
port     = http,https,80,443 

# block 설정 하지 않을 IP  대역대 
ignoreip = 127.0.0.1/8 192.168.10.0/24 

# ban (초) 시간 
bantime  = 3600 

# 최근 10분 동안 
findtime = 600 

# 설정 filter tomcat-php-404 
filter   = tomcat-php-404 

# 톰켓 로그 폴더 
logpath  = /var/log/tomcat8/localhost_access_log.*.txt 

# 재시도 회수 
maxretry = 5 

# 폴링 방식 적용 
backend = polling 

# ban 적용 
banaction = iptables-multiport 


11.3 재시작

위의 세팅 정보를 모두 설정하고

아래 명령을 통해 재시작 합니다.

 

# systemctl restart fail2ban 

============= 기타 ==========

1. jail.conf 설정 파일들에서 옵션들의 의미

ignoreip :이 매개 변수는 금지 시스템에서 무시해야하는 IP 주소를 식별합니다. 
          기본적으로 이것은 기계 자체에서 오는 트래픽을 무시하도록 설정되어 있습니다. 이는 꽤 좋은 설정입니다.
bantime :이 매개 변수는 금지의 길이를 초 단위로 설정합니다. 기본값은 600 초 또는 10 분입니다.
findtime :이 매개 변수는 fail2ban이 반복적으로 실패한 인증 시도를 찾을 때주의 할 창을 설정합니다. 
          기본값은 600 초 (다시 10 분)로 설정됩니다. 즉, 소프트웨어는 지난 10 분 동안 실패한 시도 횟수를 계산합니다.
maxretry : findtime금지가 설정되기 전에 창 에서 허용되는 실패한 시도 횟수를 설정합니다 .
backend :이 항목은 fail2ban이 로그 파일을 모니터하는 방법을 지정합니다. 
         이 설정은 autofail2ban이 시도한 pyinotify다음 gamin사용할 수있는 것을 
         기반으로 폴링 알고리즘을 시도한다는 것을 의미합니다 .
usedns : 역방향 DNS가 금지 구현을 돕는 데 사용되는지 여부를 정의합니다. 
         이것을 "no"로 설정하면 호스트 이름 대신 IP 자체가 금지됩니다. 
         "warn"설정은 역방향 DNS를 사용하여 호스트 이름을 검색하고 그런 식으로 금지하려고 시도하지만 검토를 위해 활동을 기록합니다.
destemail :이 주소는 메일 알리미로 작업을 구성한 경우 알림 메일로 전송됩니다.
           보낸 사람 이름 : 생성 된 알림 전자 메일의 보낸 사람 전자 메일 필드에 사용됩니다.
banaction : 임계 값에 도달 할 때 사용할 조치를 설정합니다. 실제로 /etc/fail2ban/action.d/호출 된 
            파일의 이름이 있습니다 iptables-multiport.conf. 
            이것은 iptablesIP 주소를 금지하기위한 실제 조작을 처리합니다 .          
mta : 알림 이메일을 보내는 데 사용할 메일 전송 에이전트입니다.
protocol : IP 금지가 구현 될 때 삭제 될 트래픽 유형입니다. 
           이것은 새로운 iptables 체인으로 전송되는 트래픽 유형이기도합니다.
chain : 이것은 fail2ban 깔때기로 트래픽을 보내기위한 점프 규칙으로 구성되는 체인입니다.


2. fail2ban 자주 사용하는 명령목록

fail2ban-client status : ban 목록확인
cat /var/log/fail2ban.log : fail2ban 로그확인
cat /var/log/fail2ban.log | grep Ban : Ban 내역만 뽑아서 보기
* ban된 ip 해제하기
fail2ban-client set sshd unbanip 192.111.1.101 (해제IP)

위에 명령어의 sshd 체인네임이며 ftp 등 ban 내역의 해제정보를 적으면된다.

3. filter 적용 테스트

모든 필터는 작성한 후 정상 동작하는지 필터 체크를 해야 합니다.

아래는 예제...

# fail2ban-regex   

예)

# fail2ban-regex /var/log/tomcat8/localhost_access_log.*.txt  /etc/fail2ban/filter.d/tomcat-php-404.conf

 
예2 ) 변수로 정의하는 방식

$ line='0.0.0.0 - - [06/Apr/2017:07:45:42 +0000] "POST /xmlrpc.php'

$ ./fail2ban-regex "$line"  ' - - \[.*\].*POST /xmlrpc' |grep ^Lines
Lines: 1 lines, 0 ignored, 1 matched, 0 missed


4. 설정된 fail2ban 상태 확인

# fail2ban-client status sshd

정상 상태이면 Journal matches 정보나 file 정보가 보이나

비정상 상태이면 안보임... ㅜㅜ


Status for the jail: mysqld-auth
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     0
|  `- Journal matches:
`- Actions
   |- Currently banned: 0
   |- Total banned:     0
   `- Banned IP list:
[root@localhost fail2ban]# fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed: 1
|  |- Total failed:     13
|  `- Journal matches:  _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
   |- Currently banned: 27
   |- Total banned:     27
   `- Banned IP list:   198.211

 

# fail2ban-client status mysqld-auth
Status for the jail: mysqld-auth
|- Filter
|  |- Currently failed: 1
|  |- Total failed:     1
|  `- File list:        /var/log/mariadb/mariadb.log
`- Actions
   |- Currently banned: 0
   |- Total banned:     0
   `- Banned IP list:

 

 


 

윈도우즈 용도 존재함 (시간나면 정리를 할 예정)

Wail2Ban

관련 : http://cloudjoon.blogspot.com/2017/08/rdp-security-wail2ban.html

 

Beyond Cloud "IBM Cloud"

RDP( 원격데스크탑 ) 무차별 대입공격 관련 보안스크립트 적용 가이드 1. Wail2ban 소개 - Wail2ban 은 Windows 운영체제 대상으로 RDP( 원격데스크탑 ) 에 대하여 Brute-forc...

cloudjoon.blogspot.com

https://developer.ibm.com/kr/cloud/softlayer-bluemix-infra/security/2017/08/31/rdp-security-script/

 

RDP(원격데스크탑) 무차별 대입공격 관련 보안스크립트 적용 가이드 - IBM Developer

1. Wail2ban 소개 – Wail2ban 은 Windows 운영체제 대상으로 RDP(원격데스크탑)에 대하여 Brute-force Attack(패스워드 무차별 대입공격) 에 대한 방어를 위한 자동 보안설정 스크립트입니다. – 해당 스크립트는 파워쉘(PowerShell)로 작성되었으며, 원작자는 “Katie McLaughlin” 로써 해당 코드에 대한 재배포시에는 반드시 하기URL 의 “Copy Right Notice” 를 명시해야 됩니다.    htt

developer.ibm.com

 

https://github.com/glasnt/wail2ban

 

glasnt/wail2ban

fail2ban, for windows. . Contribute to glasnt/wail2ban development by creating an account on GitHub.

github.com

 

 

반응형
반응형
실행 배치
shellCall.bat

start sqlplus /nolog @./tobe.sql 접속계정@tns내DB명/비밀번호

tobe.sql 파일구성
CONN &1
SET MARKUP HTML ON SPOOL ON
SET TERM OFF
SET TIMING ON
SPOOL C:₩RESULT_TOBE.XLS
SET LINESIZE 10000
SET PAGRSIZE 1

/* 특정 컬럼 사이즈 증가 */
COLUMN OBJECT_ID 999999999999999999

/* START SQL */
SELECT * FROM TAB;

SELECT * FROM DICTIONARY;

SELECT * FROM ALL_OBJECTS;

SPOOL OFF
SET MARKUP HTML OFF SPOOL OFF
EXIT;
반응형
반응형


CD 롬 정보 얻어오는 스크립트(dvd.bat)


 @echo off

set USB_DRIVE=none

for /F "tokens=1*" %%a in ('fsutil fsinfo drives') do (

   for %%c in (%%b) do (

      for /F "tokens=3" %%d in ('fsutil fsinfo drivetype %%c') do (


         if %%d equ CD-ROM (

            set DVD=%%c:

          )

          

      )

   )

)





자동으로 복구하는 BATCH 스크립트 (setup.bat)


 @echo off

setlocal


rem call 은 같은 화면 / start은 별도로 수행

call dvd.exe


::::::::::::::::::::::::::::::::::::::::::::::

:LOOP

color 1F

cls

echo.

echo.

echo.

echo.                  ◀ 고스트 자동 복구용 ▶

echo. 

echo.            1. Centos 6.3 복구 (DVD -^> resource\backup.gho)

echo.                (ID : root / password : 1q2w3e )

echo. 

echo.            2. Partition Wizard Professional Edition

echo.

echo.            3. Q-Dir

echo.

echo.            4. reboot

echo.

echo.            ※ 리눅스 복원(1번)은 모든 데이터가 지원지니

echo.

echo                제안서 작업 폴더 등은 백업 후 수행 하세요

echo.

echo                자세한 사항은 root로 로그인 한 뒤 바탕화면 참고하세요

echo.

echo.

set /p ANS="☞ 번호를 입력해 주세요 : "


if "%ANS%" == "1" goto MENU1

if "%ANS%" == "2" goto MENU2

if "%ANS%" == "3" goto MENU3

if "%ANS%" == "4" goto QUIT


goto LOOP

:::::::::::::::::::::::::::::::::::::::::::::::

:MENU1

echo.

echo. Windows 7 Restore

echo. 

start x:\ghost32 -clone,mode=load,src=%DVD%\sources\backup.gho,dst=1 -sure -fx

goto LOOP


:MENU2

echo.

echo "Partition Wizard Run~"

start x:\PartitionWizardPro6.exe

echo.

goto LOOP


:MENU3

echo.

echo "Q-Dir Run"

echo.

start x:\Q-Dir.exe

goto LOOP


:QUIT

reboot






반응형
반응형

10g에 추가된 Tuning Advisor


SQL ID 와 Begin SNAP 부분은 addm 을 통해 조회하여 입력하는 것이 좋음.(자동 생성 SH은 비밀글 참조...)


출처 : http://blog.daum.net/onjshop/175


★★ www.oraclejava3.co.kr 에서 더욱 유용한 정보를 확인하실 수 있습니다. ★★ 
(분당정자점은 양재, 강남에서 15분 거리에 있습니다. ^^)

Automatic SQL Tuning in Oracle Database 10g(SQL Tuning Advisor) 

이번 강좌에서는 오라클 10g의 새 특징인 자동 SQL 튜닝기능에 대해 알아 보도록 하겠습니다. 

Normal Mode에서 오라클 옵티마이저는 아주 짧은 시간에 최적의 실행 계획을 계산해 내야 합니다. 그러므로 항상 최선의 실행 계획을 만들어 낼 수는 없다 이겁니다~^^ 

Oracle 10g는 옵티마이저가 튜닝 모드에서 실행될 수 있도록 하여 추가적인 통계 정보를 모아 추후에는 튜닝된 최적의 실행 계획을 만들어 낼 수 있도록 지원 합니다. 물론 이러한 프로세스는 하나의 SQL문장에 대해 몇 분이 걸릴지도 모르므로 리소스를 많이 잡아 먹는 경향이 있습니다. 

튜닝모드에서 옵티마이저가 하는 일에 대해 정리해 볼까요? 

- 통계 분석(statistics Analysis) : 옵티마이저는 오래전에 만들어진 통계 정보나 또는 통계 정보가 없는 부분에 대해 통계 정보를 생산 하는 것을 권고 하며 SQL Profile안에 부가적인 객체에 대한 통계 정보를 저장 합니다. 

- SQL Profileing : CBO(Cost Base Optimizer)로 수행될 때 SQL문을 위해서는 부가 정보들이 필요 합니다. 이러한 SQL문의 정보들을 SQL 프로파일이라는 형태로 수집해 놓습니다. 이러한 SQL 프로파일이 필요할 때마다 SQL Tuning Advisor 에 의해 업데이트됩니다. 

- 실행 경로 분석(Access Path Analysis) : 어떤 인덱스를 통해 데이터를 접근하여 추출할지를 결정 합니다. 필요하다면 SQL Access Advisor를 호출해 인덱스에 대한 권고를 요구하기도 합니다. 

- SQL 구조 분석(SQL Structure Analysis) : SQL문이 비효율적인 실행 계획을 생성할 경우 같은 결과를 보여줄 수 있는 비슷한 SQL문을 생성해 권고하는 역할을 합니다. 

이러한 Automatic SQL Tuning 특징은 EM(Enterprise Manager)의 “Advisor Central” 을 이용해서도 사용 할 수 있으며 또한 PL/SQL의 DBMS_SQLTUNE 패키지를 이용해서도 사용 가능 합니다. 본 강좌에서는 PL/SQL에 초점을 맞추어서 진행토록 하겠습니다. 

SQL Tuning Advisor 

SQL Tuning API에 접근 하기 위해서는 ADVISOR라는 권한이 있어야 합니다. 

아래처럼 하면 됩니다. 

SQL>CONN sys/password AS SYSDBA 
SQL>GRANT ADVISOR TO scott; 
SQL>CONN scott/tiger 

SQL Tuning Advisor를 사용하기 위한 첫 번째 단계는 CREATE_TUNING_TASK를 이용하여 새로운 tuning task를 만드는 일입니다. 분석되고자 하는 SQL 문장은 AWR이나 CURSOR CACHE, SQL Tuning set 또는 매뉴얼하게 만들어져 검색 될 수 있습니다. 

SET SERVEROUTPUT ON 

-- Tuning task created for specific a statement from the AWR. 
DECLARE 
l_sql_tune_task_id VARCHAR2(100); 
BEGIN 
l_sql_tune_task_id := DBMS_SQLTUNE.create_tuning_task ( 
begin_snap => 764, 
end_snap => 938, 
sql_id => '19v5guvsgcd1v', 
scope => DBMS_SQLTUNE.scope_comprehensive, 
time_limit => 60, 
task_name => '19v5guvsgcd1v_AWR_tuning_task', 
description => 'Tuning task for statement 19v5guvsgcd1v in AWR.'); 
DBMS_OUTPUT.put_line('l_sql_tune_task_id: ' || l_sql_tune_task_id); 
END; 


-- Tuning task created for specific a statement from the cursor cache. 
DECLARE 
l_sql_tune_task_id VARCHAR2(100); 
BEGIN 
l_sql_tune_task_id := DBMS_SQLTUNE.create_tuning_task ( 
sql_id => '19v5guvsgcd1v', 
scope => DBMS_SQLTUNE.scope_comprehensive, 
time_limit => 60, 
task_name => '19v5guvsgcd1v_tuning_task', 
description => 'Tuning task for statement 19v5guvsgcd1v.'); 
DBMS_OUTPUT.put_line('l_sql_tune_task_id: ' || l_sql_tune_task_id); 
END; 


-- Tuning task created from an SQL tuning set. 
DECLARE 
l_sql_tune_task_id VARCHAR2(100); 
BEGIN 
l_sql_tune_task_id := DBMS_SQLTUNE.create_tuning_task ( 
sqlset_name => 'test_sql_tuning_set', 
scope => DBMS_SQLTUNE.scope_comprehensive, 
time_limit => 60, 
task_name => 'sqlset_tuning_task', 
description => 'Tuning task for an SQL tuning set.'); 
DBMS_OUTPUT.put_line('l_sql_tune_task_id: ' || l_sql_tune_task_id); 
END; 


-- Tuning task created for a manually specified statement. 
DECLARE 
l_sql VARCHAR2(500); 
l_sql_tune_task_id VARCHAR2(100); 
BEGIN 
l_sql := 'SELECT e.*, d.* ' || 
'FROM emp e JOIN dept d ON e.deptno = d.deptno ' || 
'WHERE NVL(empno, ''0'') = :empno'; 

l_sql_tune_task_id := DBMS_SQLTUNE.create_tuning_task ( 
sql_text => l_sql, 
bind_list => sql_binds(anydata.ConvertNumber(100)), 
user_name => 'scott', 
scope => DBMS_SQLTUNE.scope_comprehensive, 
time_limit => 60, 
task_name => 'emp_dept_tuning_task', 
description => 'Tuning task for an EMP to DEPT join query.'); 
DBMS_OUTPUT.put_line('l_sql_tune_task_id: ' || l_sql_tune_task_id); 
END; 



만약 TASK_NAME 파라미터에 값이 있다면 SQL tune task의 식별자로서 사용 되구요, 생략된다면 시스템에서 “TASK_1478” 등과 같이 만들어서 리턴 합니다. 

NVL이 SQL 문장에 사용되다면 옵티마이저로부터 반작용을 유발 하며 부가적으로 그러한 테이블에 관한 통계정보를 지우는 것도 가능 합니다. 

EXEC DBMS_STATS.delete_table_stats('SCOTT','EMP'); 

위에서 tuning task에 대해 정의를 했는데 그 다음으로 할 일은 EXECUTE_TUNING_TASK procedure를 이용하는 일 입니다. 

EXEC DBMS_SQLTUNE.execute_tuning_task(task_name => 'emp_dept_tuning_task'); 

물론 아래처럼 task를 중단하거나 재시작 하거나 취소하거나 하는 것들이 가능 합니다. 

-- Interrupt and resume a tuning task. 
EXEC DBMS_SQLTUNE.interrupt_tuning_task (task_name => 'emp_dept_tuning_task'); 
EXEC DBMS_SQLTUNE.resume_tuning_task (task_name => 'emp_dept_tuning_task'); 

-- Cancel a tuning task. 
EXEC DBMS_SQLTUNE.cancel_tuning_task (task_name => 'emp_dept_tuning_task'); 

-- Reset a tuning task allowing it to be re-executed. 
EXEC DBMS_SQLTUNE.reset_tuning_task (task_name => 'emp_dept_tuning_task'); 
다음과 같이 tuning task는 DBA_ADVISOER_LOG라는 뷰를 통해서 확인 가능 합니다. 

SQL>SELECT task_name, status FROM dba_advisor_log WHERE owner = 'SCOTT'; 

TASK_NAME STATUS 
------------------------------ ----------- 
emp_dept_tuning_task COMPLETED 

tuning task가 성공적으로 수행 되면 REPORT_TUNING_TASK라는 함수를 통해 권고를 확인 할 수 있습니다. 

SET LONG 10000; 
SET PAGESIZE 1000 
SET LINESIZE 200 
SELECT DBMS_SQLTUNE.report_tuning_task('emp_dept_tuning_task') AS recommendations FROM dual; 
SET PAGESIZE 24 

결과는 아래와 같습니다. 

RECOMMENDATIONS 
-------------------------------------------------------------------------------- 
GENERAL INFORMATION SECTION 
------------------------------------------------------------------------------- 
Tuning Task Name : emp_dept_tuning_task 
Scope : COMPREHENSIVE 
Time Limit(seconds): 60 
Completion Status : COMPLETED 
Started at : 05/06/2004 09:29:13 
Completed at : 05/06/2004 09:29:15 

------------------------------------------------------------------------------- 
SQL ID : 0wrmfv2yvswx1 
SQL Text: SELECT e.*, d.* FROM emp e JOIN dept d ON e.deptno = d.deptno 
WHERE NVL(empno, '0') = :empno 

------------------------------------------------------------------------------- 
FINDINGS SECTION (2 findings) 
------------------------------------------------------------------------------- 

1- Statistics Finding 
--------------------- 
Table "SCOTT"."EMP" and its indices were not analyzed. 

Recommendation 
-------------- 
Consider collecting optimizer statistics for this table and its indices. 
execute dbms_stats.gather_table_stats(ownname => 'SCOTT', tabname => 
'EMP', estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE, 
method_opt => 'FOR ALL COLUMNS SIZE AUTO', cascade => TRUE) 

Rationale 
--------- 
The optimizer requires up-to-date statistics for the table and its indices 
in order to select a good execution plan. 

2- Restructure SQL finding (see plan 1 in explain plans section) 
---------------------------------------------------------------- 
The predicate NVL("E"."EMPNO",0)=:B1 used at line ID 2 of the execution plan 
contains an __EXPRESSION!__ on indexed column "EMPNO". This __EXPRESSION!__ prevents 
the optimizer from selecting indices on table "SCOTT"."EMP". 

Recommendation 
-------------- 
Rewrite the predicate into an equivalent form to take advantage of 
indices. Alternatively, create a function-based index on the __EXPRESSION!__. 

Rationale 
--------- 
The optimizer is unable to use an index if the predicate is an inequality 
condition or if there is an __EXPRESSION!__ or an implicit data type conversion 
on the indexed column. 

------------------------------------------------------------------------------- 
EXPLAIN PLANS SECTION 
------------------------------------------------------------------------------- 

1- Original 
----------- 
Plan hash value: 1863486531 

---------------------------------------------------------------------------------------- 
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | 
---------------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT | | 1 | 107 | 4 (0)| 00:00:01 | 
| 1 | NESTED LOOPS | | 1 | 107 | 4 (0)| 00:00:01 | 
| 2 | TABLE ACCESS FULL | EMP | 1 | 87 | 3 (0)| 00:00:01 | 
| 3 | TABLE ACCESS BY INDEX ROWID| DEPT | 1 | 20 | 1 (0)| 00:00:01 | 
| 4 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)| 00:00:01 | 
---------------------------------------------------------------------------------------- 

Note 
----- 
- dynamic sampling used for this statement 

------------------------------------------------------------------------------- 


1 row selected. 

Tuning task는 다음처럼 DROP 합니다. 

BEGIN 
DBMS_SQLTUNE.drop_tuning_task (task_name => '19v5guvsgcd1v_AWR_tuning_task'); 
DBMS_SQLTUNE.drop_tuning_task (task_name => '19v5guvsgcd1v_tuning_task'); 
DBMS_SQLTUNE.drop_tuning_task (task_name => 'sqlset_tuning_task'); 
DBMS_SQLTUNE.drop_tuning_task (task_name => 'emp_dept_tuning_task'); 
END; 

 

반응형

'Database > ORACLE' 카테고리의 다른 글

one port multi listener 설정 하기  (0) 2012.12.24
DBMS_XPLAN 정보 조회  (0) 2012.09.07
10G OCP 자격증 관련  (2) 2012.07.08
오라클 패키지 CURSOR(커서) 출력 값 조회  (0) 2012.02.23
일정 시간 별로 쿼리 정보 조회  (0) 2012.02.20
반응형

개발자의 실수를 줄여주는 java.sql.Connection 만들기







흔히 close()를 하지 않아서 발생하는 자원 누수 현상을 줄여주는 Connection 클래스를 만들어본다.

JDBC API 사용시 흔히 하는 개발자의 실수

JDBC API를 사용하여 데이터베이스 프로그래밍을 할 때 가장 많이 사용되는 코드는 아마도 다음과 같은 형태일 것이다.

    Connection conn = null;
    Statement stmt = null;
    ResultSet rs = null;
    
    try {
        conn = DBPool.getConnection(); //
        stmt = conn.createStatement();
        rs = stmt.executeQuery(..);
        ...
    } catch(SQLException ex) {
        // 예외 처리
    } finally {
        if (rs != null) try { rs.close(); } catch(SQLException ex) {}
        if (stmt != null) try { stmt.close(); } catch(SQLException ex) {}
        if (conn != null) try { conn.close(); } catch(SQLException ex) {}
    }

그런데 위와 같은 프로그래밍을 할 때 흔히 하는 실수가 close()를 제대로 해 주지 않는 것이다. 특히, 하나의 메소드에서 5-10개의 (PreparedStatement를 포함한)Statement와 ResultSet을 사용하는 경우에는 개발자의 실수로 같은 Statement를 두번 close() 하고 한두개의 Statement나 ResultSet은 닫지 않는 실수를 하곤 한다. 이처럼 close() 메소드를 알맞게 호출해주지 않을 경우에는 다음과 같은 문제가 발생한다.

  1. Statement를 닫지 않을 경우, 생성된 Statement의 개수가 증가하여 더 이상 Statement를 생성할 수 없게 된다.
  2. close() 하지 않으므로 불필요한 자원(네트워크 및 메모리)을 낭비하게 된다.
  3. 커넥션 풀을 사용하지 않는 상황에서 Connection을 닫지 않으면 결국엔 DBMS에 연결된 새로운 Connection을 생성할 수 없게 된다.
위의 문제중 첫번째와 두번째 문제는 시간이 지나면 가비지 콜렉터에 의해서 해결될 수도 있지만, 만약 커넥션 풀을 사용하고 있다면 그나마 가비지 콜렉션도 되지 않는다. 따라서 커넥션 풀을 사용하는 경우 Statement와 ResultSet은 반드시 닫아주어야만 한다. 하지만, 제아무리 실력이 뛰어난 개발자라 할지라도 각각 수십에서 수백줄로 구성된 수십여개의 .java 파일을 모두 완벽하게 코딩할 수는 없으며, 따라서 한두군데는 close()를 안 하기 마련이다. 운이 좋으면 빨리 찾을 수 있겠지만, 그렇지 않다면 close() 안한 부분을 찾는 데 몇십분, 몇시간, 심한 경우 1-2일 정도가 걸리기도 한다.

Statement를 자동으로 닫아주는 MVConnection 클래스 구현

실제로 필자도 앞에서 언근했던 문제들 때문에 고생하는 사람들을 종종 봐 왔었으며, 그때마다 그 버그를 고치기 위해서 소스 코드를 일일이 찾아보는 노가다를 하는 개발자들을 보기도 했다. 그래서 만든 클래스가 있는데, 그 클래스의 이름을 MVConnection이라고 붙였다. 이름이야 여러분의 입맛에 맛게 수정하면 되는 것이므로, 여기서는 원리만 간단하게 설명하도록 하겠다.

먼저, MVConnection을 구현하기 전에 우리가 알고 있어야 하는 기본 사항이 있다. JDBC API를 유심히 읽어본 사람이라면 다음과 같은 내용을 본 적이 있을 것이다.

  • Statement를 close() 하면 Statement의 현재(즉, 가장 최근에 생성한) ResultSet도 close() 된다.
  • ResultSet은 그 ResultSet을 생성한 Statement가 닫히거나, 또는 executeQuery 메소드를 실행하는 경우 close() 된다.
MVConnection은 바로 이 두가지 특성을 사용한다. 위의 두 가지 특징을 정리하면 결국 Statement만 알맞게 닫아주면 그와 관련된 ResultSet은 자동으로 닫힌다는 것을 알 수 있다. 따라서 ConnectionWrapper 클래스는 Connection이 생성한 Statement들만 잘 보관해두었다가 각 Statement를 닫아주기만 하면 되는 것이다. PreparedStatement나 CallableStatement는 Statement를 상속하고 있으므로 따로 처리할 필요 없이 Statement 타입으로 모두 처리할 수 있으므로, PreparedStatement와 CallableStatement를 위한 별도의 코드는 필요하지 않다.

다음은 MVConnection 클래스의 핵심 코드이다.

    public class MVConnection implements Connection {
        
        private Connection conn; // 실제 커넥션
        
        private java.util.List statementList; // statement를 저장
        
        public MVConnection(Connection conn) {
            this.conn = conn;
            statementList = new java.util.ArrayList();
        }
        
        public void closeAll() {
            for (int i = 0 ; i < statementList.size() ; i++) {
                Statement stmt = (Statement)statementList.get(i);
                try {
                    stmt.close();
                } catch(SQLException ex) {}
            }
        }
        
        public void close() throws SQLException {
            this.closeAll();            conn.close();
        }
        
        public Statement createStatement() throws SQLException {
            Statement stmt = conn.createStatement();
            statementList.add(stmt);
            return stmt;
        }
        
        public CallableStatement prepareCall(String sql) throws SQLException {
            CallableStatement cstmt = conn.prepareCall(sql);
            statementList.add(cstmt);
            return cstmt;
        }
        
        public PreparedStatement prepareStatement(String sql) throws SQLException {
            PreparedStatement pstmt = conn.prepareStatement(sql);
            statementList.add(pstmt);
            return pstmt;
        }
        
        ...
    }

위 코드를 보면 Statement를 저장하기 위한 List와 그 List에 저장된 Statement 객체를 모두 닫아주는 closeAll() 이라는 메소드가 정의되어 있다. 바로 이 List와 closeAll() 메소드가 이 MVConnection 클래스의 핵심이다. Statement를 생성해주는 메소드(createStatement, prepareCall, prepareStatement)를 보면 생성된 Statement를 statemetList에 추가해주는 것을 알 수 있다. 이렇게 저장된 Statement는 실제로 Connection을 닫을 때, 즉 Connection의 close() 메소드를 호출할 때 닫힌다. (코드를 보면 close() 메소드에서 closeAll() 메소드를 먼저 호출하고 있다.) 따라서, close() 메소드만 호출하면 그와 관련된 모든 Statement와 ResultSet은 자동으로 닫히게 된다.

위 코드에서 다른 메소드들은 모두 다음과 같이 간단하게 구현된다.

    public boolean getAutoCommit() throws SQLException {
        return conn.getAutoCommit();
    }

MVConnection은 java.sql.Connection을 implements 하기 때문에, 그 자체가 Connection으로 사용될 수 있다. 따라서 MVConnection을 사용한다고 해서 특별히 코드가 많이 변경되지는 않으며 다음과 같이 전체적으로 코드가 단순하게 바뀐다.

    Connection conn = null;
    Statement stmt = null;
    ResultSet rs = null;
    
    try {
        // MV Connection 생성
        conn = new MVConnection(DBPool.getConnection());
        stmt = conn.createStatement();
        rs = stmt.executeQuery(..);
        ...
    } catch(SQLException ex) {
        // 예외 처리
    } finally {
        // conn 만 close() 해준다.
        if (conn != null) try { conn.close(); } catch(SQLException ex) {}
    }

때에 따라서는 Connection을 close() 하지 않고 커넥션 풀에 되돌려 놔야 할 때가 있다. 그런 경우에는 다음과 같은 형태의 코드를 사용하면 된다.

    Connection conn = null;
    Statement stmt = null;
    ResultSet rs = null;
    
    try {
        // MV Connection 생성
        conn = new MVConnection(DBPool.getConnection());
        stmt = conn.createStatement();
        rs = stmt.executeQuery(..);
        ...
    } catch(SQLException ex) {
        // 예외 처리
    } finally {
        if (conn != null) try { 
            ((MVConnection)conn).closeAll();
            DBPool.returnConnection(conn);
        } catch(SQLException ex) {}
    }

즉, Connection을 닫지 않는 경우에는 위와 같이 커넥션 풀에 반환하기 전에 closeAll() 메소드 하나만을 호출해주면 된다. 그러면 Connection과 관련된 모든 Statement, ResultSet 등이 닫히게 된다.

결론

필자의 경우는 이 글에서 작성한 MVConnection을 실제 프로젝트에 응용하여 코드 작성의 편리함 뿐만 아니라 실수로 인해서 발생하는 시스템의 버그 문제를 어느 정도 해결할 수 있었다. 특히, Statement를 생성하거나 ResultSet을 생성할 때 발생하는 커서부족 문제를 획기적으로 줄일 수 있었다. 여러분도 이 클래스를 응용하여 보다 나은 방법으로 코딩의 실수 및 자원의 낭비를 줄일 수 있는 클래스를 작성해보기 바란다.

반응형
반응형

프로폐셔널 : proplus.ww
엔터프라이즈 : enterprise.ww

폴더안에 있는 config.xml 파일을 연다


<!-- <Display Level="none" CompletionNotice="no" SuppressModal="yes" AcceptEula="yes" /> -->
디스플레이 레벨을 NONE 으로  -  설치유형 선택 : 지금설치, 사용자 설치 버튼이 안나옴.

<!-- <PIDKEY Value="이곳이 시디키 넣는곳" /> -->

회사명과 이름을 적당히 넣는다.



메뉴 화면이 익숙치 않고 리소스를 많이 차지하는 느낌이 들어서 오피스 2007을 좋아하지는 않지만 많은 사람들이 사용하는 프로그램이다 보니 자동설치방법을 정리해 보겠습니다.

1. 오피스2007 파일을 구한 다음 적당한 폴더에 푼 후 command prompt 에서 아래그림처럼 "setup /admin"하고 엔터를 칩니다.

2.잠시기다리면 아래와 같은 화면이 나옵니다.
3. 그냥확인을 누른 후 아래화면에서 좌측부분의 "라이선싱 및 사용자 인터페이스"를 클릭합니다.
4. 제품키를 입력합니다. (여기서는 저작권법에 따라 보이지 않게 처리했습니다)
5. 동의함에 체크 클릭하고 수준표시는 기본으로 정합니다.
6. 아래그림처럼 모달 표시안함에 체크하고 좌측편에 있는 "기능 설치 상태 설정"을 클릭합니다.
7. 아래와 같은 화면이 나오면 "+" 표시를 눌러 기능을 펼칩니다.
8. 아래그림처럼 클릭한 후 사용하지 않는 프로그램을 모두 "사용할 수 없음"으로 바꿔 줍니다.
9. 보통은 아래 그림과 같이 워드, 엑셀, 파워포인트 정도만 사용합니다.
10.추가기능으로 바탕화면에 필요한 엑셀 아이콘을 만들어 보겠습니다. 다른 아이콘도 이 방법을 응용하시면 됩니다. 관리화면 왼편의 "바로가기 구성"을 클릭 -> 엑셀을 선택 -> 추가를 클릭합니다.
11. 아래그림처럼 대상은 "Microsoft Office Excel 2007" 로 위치는 "DesktopFolder"로 지정하고 확인을 클릭합니다.
12. 이제 지정도구는 마무리 하겠습니다. 저장을 클릭합니다.
13. 저장할 파일이름은 임의로 저장하면 되겠으나, 여기서는 추후에 적용할 자동설치를 위해 "set2007" 로 정하겠습니다.
14. 마지막으로 창을 닫으려고 하면 아래와 같은 화면이 나옵니다 .확인을 클릭하세요.
15. 폴더를 보면 아래 그림과 같이 set2007.MSP 파일이 보이면 자동설치를 위한 관리파일이 제대로 만들어진 것 입니다.
16. 설치시에 서비스팩2를 자동으로 설치되도록 하려면 Updates 폴더에 msp 파일을 넣어주기만 하면 자동적으로 설치가 됩니다.
여기서는 서비스팩2와 관련 핫픽스를 다운받을 수 있는 파일을 첨부합니다. 적당한 폴더에 o2k7up2.exe  파일을 풀고 배치파일을 실행하면 updates 폴더가 생기고 그 안에 msp 파일들이 생깁니다. 생성된 updates 폴더를 오피스 폴더로 복사하면 됩니다.

17. 마지막 문제는 이렇게 해도 자동설치가 안되고 아래그림과 같은 화면에서 "지금 설치"를 눌러줘야 진행이 된다는 것입니다.
편의상 자동으로 지금설치를 눌러주도록 오토잇으로 만든 autoset.exe  파일을 첨부합니다. 오피스 원본이 있는 폴더에 압축을 풀어주면 되겠습니다. 이 파일 때문에 위에서 관리파일 이름을 set2007.msp 로 정한 것입니다.

설명은 길지만 간단한 작업입니다. 성공하시길^^*

출처 : http://pd007.egloos.com/2959477
반응형

+ Recent posts