메뉴 건너뛰기

Bigdata, Semantic IoT, Hadoop, NoSQL

Bigdata, Hadoop ecosystem, Semantic IoT등의 프로젝트를 진행중에 습득한 내용을 정리하는 곳입니다.
필요한 분을 위해서 공개하고 있습니다. 문의사항은 gooper@gooper.com로 메일을 보내주세요.


하둡 분산 파일 시스템을 기반으로 색인하고 검색하기

- 인터넷에서 바늘 찾기 -


원문: http://www.drdobbs.com/article/print?articleID=226300241


July 29, 2010


Kashyap Santoki는 Infosys Technologies Limited에서 일한다. KashyapChimanlal_S@infosys.com로 연락할 수 있다.



오늘날에는 정보가 넘치고 있다. 지역적으로 흩어져 있는 거대한 량의 정보는 계속해서 증가하고 있으며, 이러한 정보로부터 의미있는 결과를 얻어내려면 빠르게 파싱할 수 있는 시스템을 필요로 한다. 분산 데이터를 검색할 수 있는 색인을 만들 수 있다면, 파싱작업을 하는데 큰 도움이 된다. 이 글에서는 루씬(Lucene)와 자바를 사용해서 데이터를 색인하고 검색하는 기본적인 방법을 설명한다. 또한 RAMDirectory를 사용해서 색인하고 검색하는 방법, HDFS에 저장된 데이터에 대해 색인을 생성하는 방법, 이렇게 생성된 색인을 검색하는 방법을 설명한다. 마이크로소프트 윈도우 XP SP3을 플랫폼으로 사용하며, 개발환경으로는 자바 1.6, 이클립스 3.4.2, 루씬 2.4.0, 하둡 0.19.1으로 구성된다.


색인과 검색 작업을 위해서 하둡을 사용했다. 아파치 하둡 프로젝트는 안전하며, 확장 가능한 분산 컴퓨팅을 지원하는 오픈소스 소프트웨어다. 하둡 분산 파일 시스템(HDFS)는 원거리 네트워크상에서 파일을저장하고 공유하기 위한 목적으로 만들어졌다. HDFS는 일반적인 하드웨어상에서 동작할수 있도록 만들어졌다. 또한 장애 복구와 자원 관리 기능을 제공하며, 무엇보다도 응용 데이터 접근에 대한 높은 가용성을 지원한다.



로컬 파일 시스템에 색인 만들기

우선 로컬 파일 시스템에 저장된 데이터에 대한 색인을 만들어야 한다. 이클립스를 사용해서 먼저 프로젝트를 하나 생성하고, 클래스를 하나 만든다. 그리고 필요한 JAR 파일을 모두 프로젝트에 추가한다. 아래와 같이 일반적인 웹서버에서 생성하는 로그 파일을 데이터 예제로 사용하려고 한다.

2010-04-21 02:24:01 GET /blank 200 120


위 데이터는 아래와 같은 필드로 구성된다.

  • 2010-04-21 -- 날짜 필드
  • 02:24:01 -- 시간 필드
  • GET -- 메서드 필드 (GET 또는 POST) -- 앞으로는 "cs-method"라는 용어로 사용하겠다
  • /blank -- 요청 URL 필드 -- 앞으로는 "cs-uri"라는 용어로 부르겠다
  • 200 -- 요청에 대한 상태 코드 -- "sc-status"라고 부르겠다
  • 120 -- 처리 시간 필드 (요청을 처리하는데 걸린 시간)


샘플 파일은 "E:DataFile" 디렉토리에 "Test.txt"라는 이름으로 저장되어 있다고 가정한다. 샘플 파일의 내용ㅇ은 다음과 같다.

2010-04-21 02:24:01 GET /blank 200 120

2010-04-21 02:24:01 GET /US/registrationFrame 200 605

2010-04-21 02:24:02 GET /US/kids/boys 200 785

2010-04-21 02:24:02 POST /blank 304 56

2010-04-21 02:24:04 GET /blank 304 233

2010-04-21 02:24:04 GET /blank 500 567

2010-04-21 02:24:04 GET /blank 200 897

2010-04-21 02:24:04 POST /blank 200 567

2010-04-21 02:24:05 GET /US/search 200 658

2010-04-21 02:24:05 POST /US/shop 200 768

2010-04-21 02:24:05 GET /blank 200 347


"Test.txt" 파일에 저장된 데이터에 대해 색인을 만들어서, 색인을 로컬 파일 시스템에 저장하려고 한다. 아래의 자바 코드를 사용하면 이 작업을 할 수 있다. (각 코드가 어떤 작업을 처리하는지 주석을 상세히 달아 두었다).

// IndexWriter 객체를 생성한다. 이때 색인 파일을 저장할 경로를 인자로 전달한다.
IndexWriter indexWriter = new IndexWriter("E://DataFile/IndexFiles", new StandardAnalyzer(), true);

// BufferedReader 객체를 생성한다. 이때 색인하려고 하는 데이터가 저장된 파일 경로를 인자로 전달한다.

BufferedReader reader= new BufferedReader(new FileReader("E://DataFile/Test.txt"));


String row=null;

// 데이터 파일에서 각 라인을 읽는다.

while ((row=reader.readLine())!= null) {
// 한 행을 분할하여 각 필드를 구한 후, 배열에 저장한다. 데이터 파일에서 사용한 구분자는 공백 문자다
String Arow[] = row.split(" ");

// 각 행에 대해 document 객체를 생성한 후, 해당 document 객체에 필드를 데이터로 추가한다.
org.apache.lucene.document.Document document = new org.apache.lucene.document.Document();

document.add(new Field("date",Arow[0],Field.Store.YES,Field.Index.ANALYZED));
document.add(new Field("time",Arow[1],Field.Store.YES,Field.Index.ANALYZED));
document.add(new Field ("cs-method",Arow[2],Field.Store.YES,Field.Index.ANALYZED));
document.add(new Field ("cs-uri",Arow[3],Field.Store.YES,Field.Index.ANALYZED));
document.add(new Field ("sc-status",Arow[4],Field.Store.YES,Field.Index.ANALYZED));
document.add(new Field ("time-taken",Arow[5],Field.Store.YES,Field.Index.ANALYZED));

// document 객체를 색인 파일에 추가한다.
indexWriter.addDocument(document);
}
indexWriter.optimize();
indexWriter.close();
reader.close();



로컬 색인 파일 검색하기

이제 생성된 색인 파일에서 데이터를 검색할 수 있다. 기본적으로 검색은 "필드" 데이터 기반으로 수행된다. 루씬 검색 엔진에서 지원하는 다양한 검색 기능을 사용해서 검색할 수 있다. 또한 특정 필드 하나만을 사용할 수도 있고, 필드를 조합해서 검색할 수도 있다. 아래의 자바 코드는 색인을 검색하는 코드다.

// Searcher객체를 생성한다. 이때 인덱스 파일이 저장된 경로를 인자로 전달한다.

Searcher searcher = new IndexSearcher("E://DataFile/IndexFiles");
Analyzer analyzer = new StandardAnalyzer();


// 현재 색인 파일에 저장된 문서 또는 항목의 총 갯술르 출력한다.
System.out.println("Total Documents = "+searcher.maxDoc()) ;


// QueryParser 객체를 생성한다. 이때 검색을 적용할 필드명을 인자로 전달한다.

QueryParser parser = new QueryParser("cs-uri", analyzer);


//Query 객체를 생성한다. 이때 검색할 텍스트를 인자로 전달한다.
Query query = parser.parse("/blank");


// 아래의 문장을 실행하면 색인 파일에서 검색을 수행하게 된다.
Hits hits = searcher.search(query);


// 검색 질의문에 매칭하는 문서 또는 항목의 갯수를 출력한다.
System.out.println("Number of matching documents = "+ hits.length());


// 검색 조건에 매칭하는 문서(똔느 파일의 행)을 출력한다.

for (int i = 0; i < hits.length(); i++) {
Document doc = hits.doc(i);
System.out.println(doc.get("date")+" "+ doc.get("time")+ " "+
doc.get("cs-method")+ " "+ doc.get("cs-uri")+ " "+ doc.get("sc-status")+ " "+ doc.get("time-taken"));
}


이 예제에서는 cs-uri 필드에 대해, 해당 cs-uri 필드에 /blank가 포함된 텍스트를 검색했다. 따라서 검색 코드가 실행되면 cs-uri 필드에 /blank가 포함된 모든 문서(또는 행)이 결과로 출력된다. 출력은 아래와 같다.

Total Documents = 11
Number of matching documents = 7
2010-04-21 02:24:01 GET /blank 200 120
2010-04-21 02:24:02 POST /blank 304 56
2010-04-21 02:24:04 GET /blank 304 233
2010-04-21 02:24:04 GET /blank 500 567
2010-04-21 02:24:04 GET /blank 200 897
2010-04-21 02:24:04 POST /blank 200 567
2010-04-21 02:24:05 GET /blank 200 347



HDFS에서 메모리 기반으로 색인하기

이제 데이터가 HDFS와 같은 분산 파일 시스템에 저장된 경우를 생각해보자. 이처럼 데이터가 분산된 경우에는 앞에서 설명한 색인 파일을 바로 생성하는 코드는 제대로 동작하지 않는다. 따라서 색인 파일을 생성하기 전에 먼저 몇가지 작업을 우선 처리해야 한다. 예를 들어 HDFS에 저장된 데이터를 로컬 파일 시스템에 복사하는 작업, 로컬 파일 시스템에 해당 데이터에 대한 색인을 생성하는 작업, 마지막으로 색인 파일을 다시 HDFS에 저장하는 단계를 거쳐야 한다. 검색할 때도 비슷한 과정을 따라 처리해야 한다. 하지만 이러한 작업은 시간이 오래 걸릴뿐더러, 그리 좋은 방식이 아니다. 대신에 데이터가 저장되어 있는 HDFS 노드의 메모리를 사용해서 데이터를 색인하고 검색해보자.


앞에서 사용했던 데이터 파일인 "Test.txt"가 이제 HDFS의 "/DataFile/Test.txt" 경로에 저장되어 있다고 가정하자. 그리고 생성된 색인 파일을 저장할 디렉토리를 HDFS의 "/IndexFiles"에 생성하자. 아래의 자바 코드는 HDFS에 저장된 파일에 대해 색인 파일을 메모리에 생성하는 코드다.

// 인덱스 파일을 생성할 경로

String Index_DIR="/IndexFiles/";

// 데이터 파일이 저장된 경로

String File_DIR="/DataFile/test.txt";


// HDFS에 접근하기 위한 FileSystem 객체를 생성한다.

Configuration config = new Configuration();
config.set("fs.default.name","hdfs://127.0.0.1:9000/");
FileSystem dfs = FileSystem.get(config);


// 색인을 메모리에 생성할 RAMDirectory (메모리) 객체를 생성한다.

RAMDirectory rdir = new RAMDirectory();


// RAMDirectory 객체에 대한 IndexWriter 객체를 생성한다.
IndexWriter indexWriter = new IndexWriter (rdir, new StandardAnalyzer(), true);


// FSDataInputStream 객체를 생성한다. 이 객체를 사용해서 HDFS에 저장된 "Test.txt" 파일을 읽어들인다.
FSDataInputStream filereader = dfs.open(new Path(dfs.getWorkingDirectory()+ File_DIR));


String row=null;


// 파일에서 한 행씩 읽어들인다.

while ((row=reader.readLine())!=null) {

// 한 행을 분할하여 각 필드를 구한 후, 배열에 저장한다. 데이터 파일에서 사용한 구분자는 공백 문자다.

String Arow[]=row.split(" ");

// 각 행에 대해 document 객체를 생성한 후, 해당 document 객체에 필드를 데이터로 추가한다.
org.apache.lucene.document.Document document = new org.apache.lucene.document.Document();

document.add(new Field("date",Arow[0],Field.Store.YES,Field.Index.ANALYZED));
document.add(new Field("time",Arow[1],Field.Store.YES,Field.Index.ANALYZED));
document.add(new Field ("cs-method",Arow[2],Field.Store.YES,Field.Index.ANALYZED));
document.add(new Field ("cs-uri",Arow[3],Field.Store.YES,Field.Index.ANALYZED));
document.add(new Field ("sc-status",Arow[4],Field.Store.YES,Field.Index.ANALYZED));
document.add(new Field ("time-taken",Arow[5],Field.Store.YES,Field.Index.ANALYZED));

// document 객체를 색인 파일에 추가한다.
indexWriter.addDocument(document);
}


indexWriter.optimize();
indexWriter.close();
reader.close();


보다시피 "Test.txt" 파일은 HDFS에 저장되어 있으며, 이 파일에 대한 색인을 메모리에 생성했다. 색인 파일을 HDFS 디렉토리에 저장하려면.

// 메모리에 저장된 파일을 배열로 얻기

String fileList[]=rdir.list();


// 메모리로부터 인덱스를 읽어서 HDFS에 저장한다.
for (int i = 0; I < fileList.length; i++) {
IndexInput indxfile = rdir.openInput(fileList[i].trim());
long len = indxfile.length();
int len1 = (int) len;


// 파일로부터 바이트 배열로 데이터를 읽어들인다.

byte[] bytarr = new byte[len1];
indxfile.readBytes(bytarr, 0, len1);


// HDFS 디렉토리에 해당 인덱스 파일명을 가진 파일을 생성한다.
Path src = new Path(dfs.getWorkingDirectory()+Index_DIR+ fileList[i].trim());
dfs.createNewFile(src);


// 바이트 배열로부터 HDFS로 데이터를 쓴다.

FSDataOutputStream fs = dfs.create(new Path(dfs.getWorkingDirectory()+Index_DIR+fileList[i].trim()),true);
fs.write(bytarr);
fs.close();
}
dfs.closeAll();


이 작업을 통해 데이터 파일인 "Test.txt"에 필요한 색인 파일을 생성한 후, 생성된 색인 파일을 HDFS 디렉토리에 저장했다.



HDFS에서 메모리 기반으로 검색하기

이제 HDFS에 저장된 색인 파일에 대해 검색을 할 수 있다. 검색을 하려면 우선 HDFS에 저장된 색인 파일을 메모리로 올려야 한다. 아래의 코드를 보자.

// HDFS에 접근하기 위한 FileSystem 객체를 생성한다.
Configuration config = new Configuration();
config.set("fs.default.name","hdfs://127.0.0.1:9000/");
FileSystem dfs = FileSystem.get(config);

// 색인을 메모리에 생성할 RAMDirectory (메모리) 객체를 생성한다.
RAMDirectory rdir = new RAMDirectory();

// 해당 디렉토리에 저장된 색인의 목록을 얻어서 배열로 저장한다.
Path pth = new Path(dfs.getWorkingDirectory()+Index_DIR);
FileSystemDirectory fsdir = new FileSystemDirectory(dfs,pth,false,config);
String filelst[] = fsdir.list();

FSDataInputStream filereader = null;
for (int i = 0; i<filelst.length; i++) {
// HDFS 디렉토리에 저장된 색인 파일로부터 데이터를 filereader로 읽어들인다.

filereader = dfs.open(new Path(dfs.getWorkingDirectory()+Index_DIR+filelst[i]));

int size = filereader.available();


// 파일로부터 배열로 데이터를 읽어들인다.

byte[] bytarr = new byte[size];

filereader.read(bytarr, 0, size);


// RAMDirector에 파일을 생성한다. 이때 HDFS에 저장된 색인 파일의 이름과 동일한 이름으로 생성한다.

IndexOutput indxout = rdir.createOutput(filelst[i]);


// 바이트 배열로부터 RAMDirectory의 파일로 데이터를 쓴다.

indxout.writeBytes(bytarr,bytarr.length);
indxout.flush();
indxout.close();
}


filereader.close();


이제 필요한 색인 파일은 모두 RAMDirectory(또는 메모리)에 존재하게 된다. 따라서 해당 색인 파일에 대해 검색을 직접 실행할 수 있다. RAMDirectory에 대한 검색 코드는 로컬 파일 시스템에 대해 검색했던 코드와 유사하다. 유일한 차이점은 Searcher 객체를 생성할 때, 로컬 파일 시스템의 디렉토리 경로가 아니라 RAMDirectory 객체(rdir)를 사용해서 생성한다는 점이다.

Searcher searcher = new IndexSearcher(rdir);
Analyzer analyzer = new StandardAnalyzer();

System.out.println("Total Documents = "+searcher.maxDoc()) ;
QueryParser parser = new QueryParser("time", analyzer);

Query query = parser.parse("02\:24\:04");

Hits hits = searcher.search(query);

System.out.println("Number of matching documents = "+ hits.length());

for (int i = 0; i < hits.length(); i++) {
Document doc = hits.doc(i);
System.out.println(doc.get("date")+" "+ doc.get("time")+ " "+
doc.get("cs-method")+ " "+ doc.get("cs-uri")+ " "+ doc.get("sc-status")+ " "+ doc.get("time-taken"));
}


아래의 출력결과에서 보다시피, "time" 필드에 대해 "02:\:24\:04." 텍스트를 사용해서 검색을 수행한다. 따라서 코드가 실행되면, "time"에 "02:\:24\:04."가 포함된 문서(또는 행)이 결과로 출력된다.

Total Documents = 11
Number of matching documents = 4
2010-04-21 02:24:04 GET /blank 304 233
2010-04-21 02:24:04 GET /blank 500 567
2010-04-21 02:24:04 GET /blank 200 897
2010-04-21 02:24:04 POST /blank 200 567



결론

HDFS와 같은 분산 파일 시스템은 오늘날 만들어지는 방대한 량의 데이터를 저장하고 처리할 수 있는 막강한 도구다. 메모리 기반으로 색인하고 검색하는 방식을 사용하면, 산더미같은 데이터 중에서 정말로 원하는 데이터를 좀더 쉽게 찾을 수 있게 된다.

번호 제목 글쓴이 날짜 조회 수
29 HBase shell로 작업하기 구퍼 2013.03.15 5834
» 하둡 분산 파일 시스템을 기반으로 색인하고 검색하기 구퍼 2013.03.15 5573
27 HBASE Client API : 기본 기능 정리 file 구퍼 2013.04.01 3551
26 HBase 설치하기 – Fully-distributed 구퍼 2013.03.12 3548
25 Hbase Shell 명령 정리 구퍼 2013.04.01 3169
24 org.apache.hadoop.hbase.PleaseHoldException: Master is initializing 구퍼 2013.03.15 2668
23 HBase 설치하기 – Pseudo-distributed file 구퍼 2013.03.12 2644
22 HBase, BigTable, Cassandra Schema Design file 구퍼 2013.03.15 2506
21 hbase shell에서 컬럼값 검색하기(SingleColumnValueFilter이용) 총관리자 2014.04.25 2443
20 hbase에 필요한 jar들 구퍼 2013.04.01 2100
19 hbase shell 필드 검색 방법 총관리자 2015.05.24 1897
18 HBase 0.98.12(1.2.5) for hadoop2 설치-5대에 완전분산모드 (HDFS HA상테) 총관리자 2015.04.29 1047
17 column family삭제시 Column family 'delete' does not exist오류 발생하는 경우 총관리자 2014.04.14 931
16 Current heap configuration for MemStore and BlockCache exceeds the threshold required for successful cluster operation 총관리자 2017.07.18 892
15 SASL configuration failed: javax.security.auth.login.LoginException: java.lang.NullPointerException 오류 해결방법 총관리자 2015.04.02 701
14 secureCRT에서 backspace키가 작동하지 않는 경우 해결방법 총관리자 2015.05.11 699
13 Flume을 이용한 데이타 수집시 HBase write 성능 튜닝 file 총관리자 2016.10.31 620
12 scan의 startrow, stoprow지정하는 방법 총관리자 2015.04.08 375
11 hbase가 기동시키는 zookeeper에서 받아드리는 ip가 IPv6로 사용되는 경우가 있는데 이를 IPv4로 강제적용하는 방법 총관리자 2015.05.08 266
10 Hadoop의 Datanode를 Decommission하고 나서 HBase의 regionservers파일에 해당 노드명을 지웠는데 여전히 "Dead regionser"로 표시되는 경우 처리 총관리자 2018.01.25 238

A personal place to organize information learned during the development of such Hadoop, Hive, Hbase, Semantic IoT, etc.
We are open to the required minutes. Please send inquiries to gooper@gooper.com.

위로