본문 바로가기
프로그래밍/데이터베이스

[오라클] Protocol Violation

by 소나기_레드 2023. 3. 7.
반응형

1 / 2

출처 : http://blog.naver.com/PostView.nhn?blogId=gaggaii&logNo=140010526179

프로토콜 바이올레이션에 대한 자료가 별로 없어 자바 서비스넷의 글들과 제가 올린 답변을 한데 묶어 문서화합니다.

발생원인

JDBC Thin 연결방식에서 많이 발생됩니다. 크게 3가지로 나눌수 있습니다.

첫째, Oracle JDBC Driver의 버젼

오라클 DBMS 버젼이 8.1.X 라면 오라클 JDBC Driver 버젼은 8.1.7.1 을 쓸 것을 권장합니다. 메이저 버젼은 일치시키고 마이너 버젼을 제일 최신의 것을 써주면 좋습니다. 무턱대고 Thin-Driver 9.0.1.0 버젼을 가져다 쓰면 안됩니다.

둘째, JDBC Coding에 있어서 문제

LONG Type의 Data에서 setString(), getString()에서 종종 생깁니다.

셋째, 한글 문자셋 때문에 특정 데이터가 깨져서 특정 Row에만 생기는 문제

이부분은 javaservice.net의 손님(guest)님이 쓴글을 인용하겠습니다.

저도 같은 문제가 생겨서 끙끙거리고 있습니다. 일단 문제를 보니 자바에서 oracle로데이터를 입출력 할 때에는 ksc5601코드셋으로 변환되어 db쪽으로 들어갑니다.따라서 특정한 문자... 잌, 샾, 숖 등등의 문자는 깨어져서 들어가지요.하지만 다시 웹에서 뿌려질 때는 이러한 글자는 정상적으로 출력이 됩니다.그런데 만약 파워빌더로 짠 프로그램이나 기타 윈도우용 관리 프로그램에서 특정한문자(위의 ksc5601에서 지원하지 않는 문자)를 입력하게 되면 이문자는 DB로 들어갑니다. 하지만 당영히 깨어져서 들어가겠지요. 그리고 이문자를 윈도우용프로그램에서 읽으면 정상적으로 읽어집니다. 그러나 JDBC를 이용한 자바에서 이문자를읽으면 100% violation에러가 발생합니다. 즉, 윈도용 프로그램에서 입력한 문자는 확장완성형 문자이고 DB나 JDBC에서 사용하는 문자셋은 KSC5601이기 때문인 것 같습니다.아직 JDBC에서 이 문제를 해결할 방법을 찾지 못하고 있습니다.혹시나 아시는 분은 꼭 가르쳐주시기를 바랍니다.

다음은 제가 오라클 기술지원 센터에서 받은 답변의 일부입니다.

선생님께 발생하신 문제는 특정 코드 range의 data를 8i thin driver가 핸들링하지 못할때 발생하는 문제로 보여집니다. 특정 코드값의 data를 fetch하는 과정에서 null data packet이 발생하는데, 그런 경우 protocol violation error가 발생합니다.

보통은 Oracle JDBC Driver의 버젼이 원인인 경우가 많은데 Database를 오래전부터 써왔고 파워빌더, CGI 등 여러 C/S 어플리케이션이 접속을 하는 환경이라면 위와 같은 해결방법을 찾으셔야 합니다.

구체적 해결방법

첫번째의 경우는 지금 쓰고 있는 JDBC Driver의 버젼을 알아내고 최신의 JDBC Driver를 설치해주시면 됩니다.

Test2.java

Class.forName ("oracle.jdbc.driver.OracleDriver");

DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());

Connectioncon = DriverManager.getConnection("");

Statement stmt = null;

DatabaseMetaData dbm= con.getMetaData();

System.out.println("Product Name :"+dbm.getDatabaseProductName());

System.out.println("Product Version :"+dbm.getDatabaseProductVersion());

System.out.println("Driver Major Version :"+dbm.getDriverMajorVersion());

System.out.println("Driver Minor Version :"+dbm.getDriverMinorVersion());

System.out.println("Driver Name :"+dbm.getDriverName());

System.out.println("Driver Version :"+dbm.getDriverVersion());

현재 오라클 JDBC Thin Driver의 버젼은

8.1.6.x.

8.1.7.0

8.1.7,1 ß--- Recommended

9.0.1.0

이렇게 4개의 버젼이 오라클 홈페이지에 올라와 있습니다. 되도록이면 DBMS가 8 버젼이라면 8.1.7.1을 쓰실 것을 권장합니다. 그리고 Oracle DBMS의 Thin-Driver와 버젼을 맞춰줘야 한다는 얘기도 있지만 특별한 Operation이 아니면 서버의 JDBC Driver는 타지 않는다는 오라클 엔지니어의 답변이 있었습니다. 그냥 기존의 classes12.zip을 교체만 해주면 됩니다.

두번째 경우는 LONG 또는 에러가 나는 String 타입의 컬럼을 가져올때 getString 말고 아래와 같이 써주면 됩니다. 이 부분은 otn.oracle.co.kr에 많은 질문과 답변이 있습니다.

StringBuffer sb = new StringBuffer();

char[] buffer = new char[1024];

int bytesRead;

try {

Reader Rd = rs.getCharacterStream("TITLE");

while ((bytesRead = Rd.read(buffer, 0, 1024)) != -1)

{

sb.append(buffer, 0, bytesRead);

}

Rd.close();

} catch (IOException e) {

System.out.println(e.getMessage());

}

title = sb.toString();

세번째 경우, 자바 서비스넷(javaservice.net)에 올라오는 질문의 대부분이 이 문제인거 같습니다. 저도 SI 나간 곳에서 문제를 접하게 되었고 오라클 기술 지원에 문의해서 해결 했습니다. 이 경우 해결책은 두가지 입니다.

1. JDBC 연결 방식을 OCI로 변경한다.

2. 오라클 DBMS를 9i로 업그레이드 한다.

OCI 변경은 JDBC 접속을 하는 Node에 오라클 클라이언트를 설치 해주면 됩니다. Net8 접속환경을 만들어 주기 위해선데 DBMS의 버젼보다 높은 걸 써도 아무 문제가 없습니다. (간단히 말하자면 sql_plus가 접속할 수 있는 환경을 만들어 주는 것입니다.)

대신 OCI와 Thin을 공존해서 쓸 경우가 많으므로 (성능 차이는 거의 나지 않다는 Perfomance 보고서가 있지만 문제가 없는 부분에 까지 OCI 연결 방식을 쓸 필요는 없는 것 같습니다.

Classes12.zip에는 오라클 Thin JDBC 클래스와 OCI JDBC 클래스가 함께 들어 있으므로 가장 최신인 8.1.7.1을 쓰기 위해선 오라클 클라이언트도 8.1.7.1을 쓰길 권장합니다.

8.1.7.1이 따로 있는지는 잘 모르겠지만 OCI 연결 방식은 오라클 클라이언트 버젼과 Oracle JDBC 클래스 버젼과 같아야만 동작합니다. (저의 경우는 그래서 눈물을 머금고 8.1.7.0을 쓰고 있습니다.)

주의> 오라클 클라이언트 8.1.7.0 ß-à 오라클 JDBC Classes12.zip 8.1.7.0

버전이 안맞으면 나중에 연결 시 Unsatisfied Link Error 가 납니다.

오라클 클라이언트 설치 및 OCI 접속 환경 셋팅은 오라클 관련 문서를 찾아보시거나 해당 엔지니어에게 문의 해주세요.

접속은 tnsnames.ora 파일에 Alias를 추가해 주면 됩니다.

TOM =

(

DESCRIPTION =

(ADDRESS_LIST =

(ADDRESS = (PROTOCOL = TCP)(HOST = www.xxxx.net)(PORT = 1521))

)

(CONNECT_DATA =

(SERVICE_NAME = ORCL)

)

)

이런식으로 모든 환경 Setting 이 완료된 후

(자세한 내용은 http://www.oracle.com/kr/support/web_supports/bulletins/ 에서 검색해 보시면 됩니다. 죄송 get 방식의 url이 없는 지라…-.-)/ )

url만 바꿔 주시고 연결을 확인하시면 됩니다.

private final static String url = "jdbc:oracle:oci8:@tom";

마무리

죄송스럽게도 OCI 환경 셋팅에 관한 내용이 빠졌는데 어설프게 설명하느니 문서 보시고 하시는게 좋을 거라 생각이 됩니다.

저의 개발 환경은 솔라리스 + 오라클 8.1.6 DBMS 였고

현재 8.1.7.0 오라클 classes12.zip으로 Thin 연결과 OCI 연결을 혼용해서 쓰고 있습니다.

둘다 ConnectionPool을 적용해 문제가 되는 데이터 열이 많은 테이블에만 OCI 접근방식을 이용하고 있습니다.

음..일단 웬만한 질문에 대한 답변은 자바서비스넷의 고수님들의 글을 활용하시면 되겠습니다. 그리고 후에 제가 쓰고 있는 ConnectionPool에 대해 문서를 올리도록 하겠습니다.

(웹에서 구한 여러 ConnectionPool이 있지만 가장 신뢰성있는 ConnectionPool입니다. 전에는 맨날 오라클 커넥션 Process가 남아 톰캣 내렸다 올렸다 해야 했는데 요즘은 전혀 문제 없습니다.)

레퍼런스

오라클 기술 지원 센터 답변

자바 서비스넷 게시물 1개

2/2

출처 : https://boxmetal.tistory.com/2

제니퍼라는 툴을 활용하는 얘기지만 Protocol Violation 현상에 대한 자세한 설명이 있음.

이번 문서에서는 WAS시스템 장애의 하나의 원인으로 종종 발견되는 현상인 아래의 이슈를 어떻게 제니퍼를

이용하여 풀어가는지를 설명하고자 합니다.

"하나의 JDBC 연결(Connection)은 한순간에 하나의 어플리케이션에게만 할당되어 처리되어야 합니다.

만약, 하나의 JDBC연결을 서로다른 Thread가 각각 prepareStatement()/executeQuery()/executeUpdate()

혹은 rs.next(), rs.getString()을 수행하게 되면, 데이타베이스와 연결된 물리적인 하나의 TCP/IP JDBC

연결에 대해 TCP/IP통신하는 과정에서 protocol violation을 내게 됩니다. 이러한 현상이 발생할 때 그

결과는 다양한 결과로 나타날 수 있습니다. 단순히 해당 요청만 "SQLException: protocol violation"을

뿌리고 끝날 수도 있지만, TCP/IP 양 Tier에서 한쪽이 만약 Socket.read()상태에 있는데 그 데이타를 다른

Thread가 먼저 가로채어 버리면 Socket.read()상태에서 깨어나질 못하게 되는 것이지요.

더불어서 그 하나의 Thread만 Socket.read()에 멈추어 있는 것으로 끝나는 것이 아니라, 그 JDBC Connection

에서 파생된 다른 메소드 중 일부는 동기화블록(synchronized block)에 의해 뒤따라 들어오는 요청은

Thread MW(Monitor Wait)상태에서 또다시 대기하게 되고, 이러한 과정이 반복되어 최대 JDBC Pool 개수에

도달하거나, 혹은 WAS에서 설정된 최대 Thread개수에 도달하면 더이상 서비스를 할 수 없는 Hang상태에

빠지는 것이지요"

위와 같은 상황을 연출하는 경우의 가지수는 다양하나, 대부분 어플리케이션 설계나 혹은 JDBC Connection

Pool 구현의 미비함에서 발생합니다.

현상 재현을 위해, 다음과 같은 샘플을 사용할 것입니다.

Oracle 9i,

Oracle JDBC Driver 9i(9.2.0.4)

Hans Vergsten의 DBConnectionManager.java라는 자체제작된 JDBC Connection Pool

Tomcat 4.1.24

Jennifer 2.5.1.4

재현 시나리오:

(1) 극히 정상적인 JDBC 어플리케이션 10개를 선정하여 일정한 부하를 지속적으로 발생합니다.

(2) 극히 정상적으로 서비스가 됨을 우선 확인합니다.

(3) JDBC 연결공유가 일어나도록 의도적으로 잘못 코딩된 어플리케이션 하나를 서너번 호출합니다.

(4) 극히 정상적이던 다른 어플리케이션들 중 일부가 blocking되어 hang이 일어나는 것을 확인합니다.

의도적인 문제의 어플리케이션은 다음과 같은 구조를 갖고 있는 예제를 사용할 것입니다.

[dbhook.jsp]

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

<%@ page session="false" contentType="text/html" import="java.sql.*" %>

<%!

    private Connection conn = null;

%>

<html><body><xmp>

<%

db.DBConnectionManager mgr = db.DBConnectionManager.getInstance();

//Connection conn = null;

PreparedStatement pstmt = null;

ResultSet rs = null;

try{

    conn = mgr.getConnection("ora",5000);

    try{

        Thread.sleep(3000); // 시간차에 의해 의도적으로 공유가 잘 일으나도록 하기 위함

    }catch(Exception ex){}

         pstmt = conn.prepareStatement ("select name from pet");

        rs = pstmt.executeQuery ();

        while (rs.next())

            out.println (rs.getString (1));

    }

    catch(Exception e){

        out.println(e.toString());

    }

    finally{

        if(rs != null) try{rs.close();}catch(Exception e){}

        if(pstmt != null) try{pstmt.close();}catch(Exception e){}

        if(conn != null) try{mgr.freeConnection("ora", conn);}catch(Exception e){}

    }

%>

OK

</xmp></body></html>

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

즉, Connection conn 이라는 변수를 공유변수(JSP에서는 <%! %>부분)영역에 선언되어 있습니다. 따라서,

하나의 요청에 의해서는 문제가 발생하지 않습니다. 그러나, 동시에 연거푸 요청이 들어오면, 해당

공유변수 conn은 뒤따라 들어오는 요청에 의해 conn의 변수의 할당이 뒤바뀌게 되고, 처음 할당된

Connection은 close()되지 않고 누수(leak)가 일어납니다(그러나 누수현상자체를 이야기하려는 것은

아닙니다.) 반면 뒤따라오던 요청에 의해 서로 다른 두개의 요청은 같은 Connection을 이용하여

각자의 일을 수행한 후, 동일 connection을 각각 한번씩 두번에 걸쳐 DBConnectionManager의

freeConnection()을 통해 pool로 반환합니다.

그런데, Hans Vergsten의 JDBC Connection Pool의 구현부를 보면 freeConnection()부의 구현이 다음과

같이 되어 있습니다.

[db.DBConnectionManager.java]

....

public void freeConnection(String name, Connection con) {

    DBConnectionPool pool = (DBConnectionPool) pools.get(name);

    if (pool != null) {

        pool.freeConnection(con);

    }

}

class DBConnectionPool {

    ....

    private Vector freeConnections = new Vector();

         ....

    public void freeConnection(Connection con) {

        // Put the connection at the end of the Vector

        freeConnections.addElement(con);

        synchronized(this){

        checkedOut--;

        notifyAll();

    }

}

....

....

가용한 JDBC연결을 보관하고 있는 freeConnections 변수는 java.util.Vector로 구현되어 있습니다.

Vector는 Add할 오브젝트가 동일 레퍼런스일지라도 한번 이상 addElement()를 통해 추가될 수 있으며

Vector입장에서는 서로 다른 엘리먼트로 받아들입니다. 만약, 동일한 Connection conn 변수를 한번

이상 freeConnection("ora", conn);을 수행하게 되면, 동일 JDBC연결이 freeConnections 라는 Vector에

연거푸 두번 들어가게 된다는 것이지요.

만약, 이 상황에서 극히 정상적인 다른 어플리케이션들이 각각 가용한 연결을 달라고(getConnection())

요청했을 때, 같은 JDBC연결을 서로다른 레퍼렌스로 물고 있는 freeConnections 에서 제공하게 되고,

결국 서로다른 어플리케이션 Thread가 같은 JDBC 연결을 동시에 물고 동시에 자신의 SQL작업을 시도하게

될 것입니다.

그 때 어떤 상황이 벌어지는지 확인해 보도록 합니다.

먼저 아래는 극히 정상적인 어플리케이션 10개를 선정하여 스트레스테스트 툴을 이용하여 1초당

1.0 - 1.5 tps의 부하를 발생시킨 상황입니다. 극히 정상적으로 어플리케이션들이 수행되고 있습니다.

해당 시점에 JDBC 연결은 총 11개가 맺어져 있으며, 아래처럼 액티브한 JDBC연결개수가 2개로 나타나고

있습니다.

 

해당 그래프를 클릭해 보면, 현재 어떤 JDBC 연결이 어떤 어플리케이션에 할당되어 있는지, 현재 SQL을

수행중인지, 결과셋(ResultSet)을 패치(Fetch, rs.next()) 중인지 확인이 가능합니다.

 

(클릭하는 시간차에 의해, 그리고 앞선 이미지를 켑쳐한 시점 차이로 인해 차이가 있을 수 있습니다.)

이제 문제의 어플리케이션(dbhook.jsp)를 호출해 보겠습니다. 그러나, 한번의 호출로써는 장애가

발생하지 않습니다. 연거푸 한번 이상 눌러 JDBC Connection 공유이슈로 인해 하나의 JDBC 연결이

서로다른 Thread에 의해 할당되고, 각각 한번씩 두번의 JDBC connection Pool로의 환원(freeConnection)을

하게 되면, (pool 내부의 freeConnections Vector에 두번 들어가게 되고) 그동안 잘 돌고 있던, 일정한

부하를 받고 있던 어플리케이션들이 갑작스럽게 다음과 같이 블록킹(blocking)되는 현상이 발생합니다.

 

그런데, 위 그림과 같이 하나의 JDBC Connection이 서로다른 어플리케이션들에게 동시에 할당되어 있으면서

각자 pstmt.executeQuery()를 수행하고 있는 형국입니다. 그러한 연결이 2개가 감지되었네요.

그리고 위 어플리케이션들은 블록킹된 채 더이상 진행하지 못하고 있습니다.

위 상황을 현재 수행중인 어플리케이션 목록인 단지 액티브서비스목록에서만 보았다면 다음과 같습니다.

 

(400초가 넘게 수행중인 어플리케이션들이 보입니다. 그 수행중인 SQL쿼리도 보입니다.)

즉, 마치 SQL을 수행중인데, 단지 그 SQL이 아직 응답이 오지 않은 것처럼만 나타나는 것이지요.

그러나 아마 데이타베이스쪽에서 보면 그러한 SQL쿼리는 오래전에 이미 수행을 끝냈을 가능성이 높습니다.

그러나 위 상황을 "JDBC 연결 상태 목록"에서 확인해보면 이야기가 달라집니다. 어떻게 하나의 JDBC연결이

아래처럼 동시에 다른 어플리케이션에게 할당되어 수행될 수 있냐는 것이지요.

 

현재 해당 어플리케이션이 무엇을 하고 있는 지 확인해 보겠습니다. 해당 어플리케이션 URL부분을 클릭하면

다음과 같은 상세 STACK TRACE가 나옵니다.

 

위 그림의 아랫부분에 있는 STACK TRACE를 통해 현재 해당 Thread는 pstmt.executeQuery()를 수행하고

있으며, "SOCKET-READ:Socket[addr=127.0.0.1,port=3306,localport=23155]" 라는 마지막 STACK 정보는

현재 Socket.read() 상태에 있다는 것을 확인할 수 있습니다.

이번에 같은 JDBC연결을 물고(?)있는 옆에 있던 다른 Thread는 무엇을 하고 있는지 확인해 보겠습니다.

다음 그림과 같습니다.

즉, conn.prepareStatement(...)를 수행하고 있는 중으로 확인됩니다. 이상하죠? 왜 위에서 걸려 있을까요?

그것은 동일한 oracle.jdbc.driver.OracleConnection을 서로 다른 Thread가 하나는 executeQuery()를

수행하고 있고 나머지 하나는 conn.prepareStatement()를 수행하는 과정에서 동기화블록(synchronized)에서

대기중이라는 것을 뜻합니다.

이처럼 하나의 JDBC 연결이 어떠한 연유이든 일정한 부하를 받고 있는 상황에서 하나 이상의 어플리케이션에게

할당되어 서비스되는 상황은 대단히 위험하다는 것을 확인해 보았습니다.

그런데, 정작 더 큰 어려움이 있습니다. 위의 액티브 어플리케이션목록이나, 혹은 액티브 JDBC 연결상태에서

나타난 꼬여있는(?) 어플리케이션들이 보이기는 하는데, 그럼 그 어플리케이션들이 문제의 원인이었습니까?

아니죠. 정말 문제의 어플리케이션은 dbhook.jsp라는 어플리케이션이었습니다. 그러나, 어디에도 dbhook.jsp가

표면적으로 들어나지도 않으며 블록킹되어 있지도 않습니다. 애꿎은 뒤따라오던 정상적인 어플리케이션들만

장애상황에서 표면적으로 보일 뿐입니다.

어떻게 dbhook.jsp가 사실 문제의 원인이었는지를 보여줄 것인가가, 제니퍼를 설계할 때 아주 까탈스런

부분이었습니다. 아래의 그림에서 보면, 여러개의 어플리케이션이 하나의 JDBC연결을 물고 있는 그 연결

정보에 다음과 같이 "Illagel Access"라는 문자가 나타납니다. 그곳에 마우스를 갖다 대면 다음과 같은

문제의 어플리케이션 목록이 나타나면서 다른 Thread가 할당시킨 JDBC 연결을 비정상적으로 접근하려고

했다는 경고메세지를 함께 뿌려 주고 있습니다. 문제의 어플리케이션은 dbhook.jsp였다는 것이죠.

 

또한 이러한 요청은 [장애진단]-[에러예외사항]에서 그러한 비정상적인 접근을 시도한 예외사항에 대해

문제의 어플리케이션이 무엇인지, 소스상에서 어느위치에서 그같은 비정상적인 접근을 시도하였는가를

일자별로 기록하고 저장되어 관리됩니다. (JDBC Connection ILLEGAL ACCESS Attempted)

 
 

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

2005.11.14

Hans Vergsten의 DBConnectionManager와 같은 JDBC Connection Pool을 사용할 때 위와 같이

하나의 JDBC Connection을 서로 다른 Thread가 가져가 동시에 작업할 수 있는 조건상황은

다음과 같습니다.

(1) 앞서의 예제와 같이 코딩상에서 conn 공유를 유발하는 코딩에서 기인합니다.

(2) freeConnection()을 두번 실행하는 경우도 동일하게 발생합니다. 첫번째 freeConnection()할

때, 그 connection은 이제부터 다른 thread가 가져갈 수 있고 만약 가져갔다면, 두번째

freeConnection()을 수행하는 순간 또다시 그 connection을 가용한 pool인 Vector에 넣게

되므로, 제3의 thread가 또 가져갈 수 있게 됩니다.

(3) close할 땐 반드시 순서가 중요합니다. rs->stmt->conn 순으로 각각 close되고

반환되어야 합니다. 그렇지 않을 경우, freeConnection()을 먼저하면, 그 connection은 다른

Thread가 가져가서 사용하고 있을 수 있는데, 뒤늦게 rs.close() stmt.close() 을 실행하면

같은 조건상황이 발생하게 된다는 것이지요.

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

종종 자바서비스넷을 통해 "WAS시스템이 응답을 안해요"라면서 "Thread Dump 분석요청합니다"라고 올리신

STACK TRACE를 보면 종종 동일한 JDBC 연결이 하나 이상의 어플리케이션에 할당되어 블록킹되어 있는 경우를

보아 왔습니다. 그것을 어떻게 효율적으로 모니터링 하고 잡을 것이냐에 대한 결과물이 제니퍼 2.5.1입니다.

반응형

댓글