프로그래밍을 하다보면 고유한 값이 필요할 때가 있습니다. DB와 연관되서 받아와야 되는 key값이라면 순차적으로 만들어야 될 필요가 있을 수도 있고, 그것이 쓰레드간 동기화, 프로세스간 동기화가 보장되어야 하는 경우도 있겠지요. 멀티쓰레드 프로그래밍은 요즘 너무나 당연시 되고 있고, 여러 프로세스를 띄워 업무를 처리하게 만드는 아키텍쳐 또한 굉장히 레어한 일은 아니지요. 이런 일을 처리할 때 File로 구현한 Sequence가 필요하겠죠. Sequence는 오라클의 Sequence를 생각하시면 될거 같네요.

이전 포스팅에서 File Queue를 이용하여 NIO와 일반 IO의 퍼포먼스를 비교해 본 적이 있습니다. 이제 FileSequence를 이용하여 퍼포먼스 비교를 해보겠습니다.

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Sequence {

    private static int LONG_BYTE_LENGTH = 8;

    private ByteBuffer buffer = ByteBuffer.allocateDirect(LONG_BYTE_LENGTH);

    private String fileName = null;
    private RandomAccessFile seqFile = null;
    private FileChannel channel = null;
    private FileLock lock = null;

    public static void createSequenceFile(long value, String directory, String fileName) throws Exception
    public static Sequence getNewInstance(String directory, String fileName) throws Exception
    public static Sequence getNewInstance(String directory, String fileName, long value) throws Exception

    private IBSequence(String directory, String fileName) throws Exception

    public synchronized boolean open() throws SysException
    public synchronized long next() throws SysException
    public synchronized long currentValue() throws SysException

    private void setValue(long value) throws IOException
    private long getValue() throws IOException
}

자세한 구현은 어렵지 않으니 생략하였습니다. next() 메서드로 값을 하나 증가시키면서 그 값를 얻을 수 있으며 currentValue() 메서드로 현재 값만 얻어오고 값을 증가 시키진 않을 수도 있습니다. 다음은 일반 IO를 이용한 setValue(), getValue() 메서드의 구현입니다. 보시는바와 같이 RandomAccessFile의 메서드를 이용하여 구현하였습니다.

private void setValue(long value) throws IOException {
    seqFile.seek(0);
    seqFile.writeLong(value);
}

private long getValue() throws IOException {
    long res = 0;
    seqFile.seek(0);
    res = seqFile.readLong();
    return res;
}

다음은 NIO를 이용한 구현입니다. fileChannel과 ByteBuffer을 이용하여 구현하였습니다. 여기서 주의할 점은, 위에서 ByteBuffer.allocateDirect(LONG_BYTE_LENGTH); 로 ByteBuffer를 할당했다는 점입니다. allocateDirect() 메서드로 할당했을 때와 allocate() 메서드로 할당했을 때의 퍼포먼스를 비교해보면 큰 차이는 안나는 듯 합니다. 하지면 여전히 allocateDirect() 메서드를 이용하는 것이 더 좋은 퍼포먼스를 냅니다.

private void setValue(long value) throws IOException {
    seqFile.seek(0);

    buffer.clear();
    buffer.putLong(value);
    buffer.flip();

    channel.write(buffer);
}

private long getValue() throws IOException {
    long res = 0;
    seqFile.seek(0);

    buffer.clear();
    channel.read(buffer);
    buffer.flip();

    res = buffer.getLong();

    return res;
}

자바 NIO와 일반 IO 퍼포먼스 비교해보기

다음은 일반 I/O로 구현한 Sequence와 NIO를 이용하여 구현한 Sequence의 퍼포면서 비교 표입니다. next()메서드와 currentValue()메서드를 for문으로 1000000번 호출하면서 걸린 시간을 비교해 보았습니다. 단위는 밀리세컨드 입니다.

일반 IO로 구현 NIO (allocate) NIO (allocateDirect)
1차 87814 17016 15844
2차 85236 16982 15562
3차 87642 17454 15656

현저히 차이가 납니다. 자바 NIO는 ByteBuffer를 통한 퍼포먼스 향상은 거의 없고 Scalable하고 Efficient한 Server를 제작 할 수 있다는 것과 Non-Blocking IO를 제작 할 수 있다는 점에 의의가 있다고 말씀하시는 분들이 있습니다. 하지만 이전 포스팅에서도 확인했던 것과 같이 커널 버퍼를 이용함으로써 더 NIO에서 얻는 퍼포먼스 향상도 상당하다는 것을 알 수 있습니다.