mysql의 jdbc driver url의 connectTimeout과 socketTimeout에 대한 개념을 정리한다.
이 개념은 굳이 mysql driver뿐 아니라 http 나 간단한 소켓 프로그래밍에 대해서도 적용될 수 있다.

1) Socket Timeout

socketTimeout은 클라이언트(웹 서버)에서 mysql 서버에 대한 Connection에 대해서 데이터를 받는 것 까지의 timeout을 의미한다.

즉, client->server까지는 포함하지 않고, server->client 까지의 timeout을 의미한다고 봐야한다. 내가 마지막으로 받을 것으로 기대하고 대기하고 있는 timeout이라고 하면 된다. 소켓이 올 때까지 라는 의미는 Blocking socket operation이라는 전제가 깔려 있다는 의미이다..
다음의 메소드들처럼 blocking method가 있다는 것.

     * ServerSocket.accept();
     * SocketInputStream.read();
     * DatagramSocket.receive();


timeout시 socket은 끊어지지 않고 사용가능합니다. 단순히 java.io.InterruptedIOException만 발생합니다.

 

    /**
     * Constructor:  Connect to the MySQL server and setup a stream connection.
     *
     * @param host the hostname to connect to
     * @param port the port number that the server is listening on
     * @param props the Properties from DriverManager.getConnection()
     * @param socketFactoryClassName the socket factory to use
     * @param conn the Connection that is creating us
     * @param socketTimeout the timeout to set for the socket (0 means no
     *        timeout)
     *
     * @throws IOException if an IOException occurs during connect.
     * @throws SQLException if a database access error occurs.
     */
    public MysqlIO(String host, int port, Properties props,
        String socketFactoryClassName, MySQLConnection conn,
        int socketTimeout, int useBufferRowSizeThreshold) throws IOException, SQLException {
        this.connection = conn;
       
        if (this.connection.getEnablePacketDebug()) {
            this.packetDebugRingBuffer = new LinkedList();
        }
        this.traceProtocol = this.connection.getTraceProtocol();
       

        this.useAutoSlowLog = this.connection.getAutoSlowLog();
       
        this.useBufferRowSizeThreshold = useBufferRowSizeThreshold;
        this.useDirectRowUnpack = this.connection.getUseDirectRowUnpack();

        this.logSlowQueries = this.connection.getLogSlowQueries();

        this.reusablePacket = new Buffer(INITIAL_PACKET_SIZE);
        this.sendPacket = new Buffer(INITIAL_PACKET_SIZE);

        this.port = port;
        this.host = host;

        this.socketFactoryClassName = socketFactoryClassName;
        this.socketFactory = createSocketFactory();
        this.exceptionInterceptor = this.connection.getExceptionInterceptor();
       
        try {
         this.mysqlConnection = this.socketFactory.connect(this.host,
          this.port, props);
        
 
         if (socketTimeout != 0) {
          try {
           this.mysqlConnection.setSoTimeout(socketTimeout);
          } catch (Exception ex) {
           /* Ignore if the platform does not support it */
          }
         }
 
         this.mysqlConnection = this.socketFactory.beforeHandshake();
 
         if (this.connection.getUseReadAheadInput()) {
          this.mysqlInput = new ReadAheadInputStream(this.mysqlConnection.getInputStream(), 16384,
            this.connection.getTraceProtocol(),
            this.connection.getLog());
         } else if (this.connection.useUnbufferedInput()) {
          this.mysqlInput = this.mysqlConnection.getInputStream();
         } else {
          this.mysqlInput = new BufferedInputStream(this.mysqlConnection.getInputStream(),
            16384);
         }
 
         this.mysqlOutput = new BufferedOutputStream(this.mysqlConnection.getOutputStream(),
           16384);
 
 
         this.isInteractiveClient = this.connection.getInteractiveClient();
         this.profileSql = this.connection.getProfileSql();
         this.autoGenerateTestcaseScript = this.connection.getAutoGenerateTestcaseScript();
 
         this.needToGrabQueryFromPacket = (this.profileSql ||
           this.logSlowQueries ||
           this.autoGenerateTestcaseScript);
 
         if (this.connection.getUseNanosForElapsedTime()
     && Util.nanoTimeAvailable()) {
    this.useNanosForElapsedTime = true;
 
    this.queryTimingUnits = Messages.getString("Nanoseconds");
   } else {
    this.queryTimingUnits = Messages.getString("Milliseconds");
   }
 
   if (this.connection.getLogSlowQueries()) {
    calculateSlowQueryThreshold();
   }
        } catch (IOException ioEx) {
         throw SQLError.createCommunicationsException(this.connection, 0, 0, ioEx, getExceptionInterceptor());
        }
    }




 

내부적으로는 Socket 클래스의 setSoTimeout 을 호출합니다.

 void setSoTimeout(int timeout)

 
내부적으로 ReadAheadInputStream 클래스 에서 read하는 작업이 들어가 있다.
보통 중간에 문제가 되면, read하다가 IOException이 나게 된다. 여기서 Socket timeout을 잘 주면 좋을 것이다.

그러나 Query Timeout(statement 단에서 지정)보다 적게 주면 문제가 되니. 항상 query timeout보다 socket timeout이 길어야 한다.




2. connect timeout

connectTimout은 connect 메소드를 호출했을 때의 connection timeout을 의미한다.

connection 이 TCP/UDP 연결이 완료(established)될 까지의 timeout이다. 즉  connect(최초 또는 retry)할 때. client->server->client 까지 되돌아오는 시간을 의미한다.

 내부적으로 Socket 클래스의 connect 메소드를 호출한다.

void connect(SocketAddress endpoint, int timeout)


3. 운영

운영할 때는 너무 길게 주지 않고, 적당히 주고 있다..  DOS에 잘 방어하면서도.. 그것때문에 side effect는 적게.하는 정책을 두고 있다.

jdgc:mysql://alpha.google.com/server?useUnicode=true&characterEncoding=euckr&connectTimeout=5000&socketTimeout=5000

Posted by '김용환'
,