어제 만들던 log 파일 분석기에서 Text를 처리하는 것이 문제가 아닌, 대용량 파일 처리에 대해서 생각하지 못했던 문제가 발생되었다.
가장 큰 문제는 File를 한번에 얻어서 byte를 얻어낼 경우에 StringBuilder의 최대 사이즈를 넘어가기 때문에 사용할 수 없다는 점. 그리고 FileStream.Read 에서 읽어낼 수 있는 양 자체도 시스템에 따른 특정 사이즈에 결정이 되어있다는 것이 대용량 처리에서 가장 큰 문제가 되었다.
이 경우에 전에 C++에서는 가상 메모리 또는 File Buffer를 사용해서 일정 크기로 잘라서 읽었던 경험이 있는데. .NET에서는 어떤 방법이 좋을지 다양한 방법으로 실험을 해봤다.;
* 제약 사항
: byte 단위로 읽어야 된다.
: IIS에서 사용되고 있는 log file이기 때문에 공유 형태로 읽어야지 된다.
: 크기에 제약이 없이 처리가 가능해야지 된다.
생각했던 방법들은 다음과 같다.
1. StringBuilder를 이용한 byte 값 저장.
: 가장 처음에 생각한 방법이다. 이 방법의 경우에 파일의 크기가 커질 경우에 StringBuilder에서 OutOfMemory Exception을 발생한다. 큰 파일을 열어야지 되기 때문에 바로 퇴장.
참고로 개발 시스템에서는 2147483647 character만이 string builder의 capacity로 사용 가능.
2. 1Byte 씩 계속해서 읽어가면서 문자열을 만들어간다.
: 매우 안정적이지만, 엄청나게 느리다.
FileStrema.ReadByte() 를 계속해서 호출하기 때문에 느리게 되는데. 상당히 비효율적인 코드로 보인다.;
3. Divide and Conquer
: 거의 방법은 첫번째 방법과 비슷하지만, 특정 BufferSize를 잡아서 그 BufferSize만큼 반복해서 읽어준다. FileStream.Read() 를 호출하는 것으로 약 4096 * 100 byte 씩 한번에 읽어서 처리하는 경우에 약 130 Mbyte의 text를 처리하는데 7sec 도 안걸리게 된다. (속도에 놀라버렸다. 같은 크기의 2번째 방법은 40여분이 걸려도 끝나지 않았다.; )
File의 끝까지 크기를 잡아주기 위해서 언제나 File의 Position과 BufferSize의 비교를 통해서 안정되게 문자열을 뽑아오는데 성공했다. T-T
List<string> messages = new List<string>();
using(FileStream fs = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
Console.WriteLine("Read Text");
byte[] btDatas = new byte[BufferSize];
List<byte> readBytes = new List<byte>();
for(long index = readStartPosition; index < readEndPosition; )
{
fs.Position = index;
int readLength = Math.Min(BufferSize, (int)(readEndPosition - index));
int readCount = fs.Read(btDatas, 0, readLength);
index += readCount;
// 가장 불만인 코드. 한글자씩 얻어와서 찾는 방법 이외에는 없을까.. 라는 생각이 무척 많이 든다.
for(int i = 0 ; i < readCount ; i++)
{
Byte readByte = btDatas[i];
if(readByte == (byte)'\n')
{
string message = Encoding.UTF8.GetString(readBytes.ToArray());
messages.Add(message);
readBytes.Clear();
}
else
{
readBytes.Add(readByte);
}
}
}
}