Skip to content

[python] LMDB

Myungchul Shin edited this page Jul 21, 2016 · 43 revisions

LevelDB에 이어서 LMDB 테스트

References

  • site
  • source
    • install
    git clone https://github.com/LMDB/lmdb.git
    cd lmdb/libraries/liblmdb
    make; sudo make install
    
  • python binding
    • install
    pip install lmdb
    
    setup하면 lib 디렉토리 밑에 있는 default source가 binding된다.
    만약, 다른 버전을 설치하려면 아래와 같이 처리 
    
    export LMDB_FORCE_SYSTEM=1
    pip install lmdb
    
  • python binding documentation
  • append only btree

Motivation

  • 현재까지 테스트해본 Berkeley DB, LevelDB 등은 random search의 경우 disk i/o가 bottle neck
  • random search 속도를 높이려면 데이터가 전부 메모리에 올라가는 것이 best
  • 불가능하다면, memory map을 사용하는 라이브러리가 있는지 찾아보자.
  • 이런 과정에서 발견한 것이 LMDB

Test in python

  • make db
    • 입력파일의 사이즈가 15G이므로 map_size를 아주 크게 잡아야한다.
    대략 (입력파일사이즈) * 1.5 = 22.5 -> 24G
    
    • map_size = 24*(1024**3) = 24G
    • 시간 : 3430s = 173084 line / sec
    • db files
    20G 2014-11-18 15:49 meta.db
    
  • search db(random)
    • 시간 : 154s = 649 line / sec
    • 역시 Berkeley DB, LevelDB와 마찬가지로 cache를 지운 상태에서는 느리다.
    • 하지만, LMDB는 mmap을 사용하므로 대용량 처리를 할때 매우 효과적인다. 물론, 메모리사이즈가 24G까지 올라갈수 있음. 이때 maximum performance
    • multi processing/threading 환경에서 아주 효과적이다.
    • Environment() 혹은 open_db() 타임에 전체 db를 memory에 적재하는 것은 구조상 불가능하다. 만약 이런 needs가 있다면 어떻게 할것인가?
      • c에서
      mdb.c 파일을 열어보면 아래와 같이 되어 있다.
      
      env->me_map = mmap(addr, env->me_mapsize, prot, MAP_SHARED, env->me_fd, 0);
      ...
      void *m = mmap(NULL, rsize, PROT_READ|PROT_WRITE, MAP_SHARED, env->me_lfd, 0);
       
      mmap은 'MAP_PRIVATE | MAP_POPULATE' 옵션이 아닌 이상 'read-ahead' 지원하지 않는다. 
      그런데 소스가 저렇게 되어 있으므로, 소스를 수정하지 않는 이상 힘들다.
      참고 : http://techoverflow.net/blog/2013/08/21/a-simple-mmap-readonly-example/
      따라서, mmap flags를 수정해서 한번에 메모리에 올리는 것도 가능하다. 
      
      또다른 방법으로는 아래 링크에 설명된  처럼, mmap 영역 전체를 page 단위로 점프하면서 첫번째 바이트만
      읽는 방법도 있음
      http://stackoverflow.com/a/752269
      
      void load_mmap(char *mmapPtr) {
          // We'll load 10MB of data from mmap
          int offset = 0;
          for(int offset = 0; offset < 10 * 1024 * 1024; offset += 4 * 1024) {
              char *p = mmapPtr + offset;
              // deref pointer to force mmap load
              char c = *p;
          }
      }
      • 수정없이
        • 입력파일은 bigram 데이터이고 빈도수를 가지고 있다. 이 빈도수가 클수록 앞으로 들어올 확률이 높다는 의미. 따라서, 프로세스를 띄운 상태에서 적당한 빈도수 이상의 bigram을 query로 날려서, 강제로 memory에 적재시키는 방법도 유용하다.
        • bigram 파일이 수정되었을때, 프로세스를 재시작해야하는 경우, 프로세스 재시작 이후에 이런 종류의 처리를 해준다는 의미

Test in c

  • make db
    • 시간 : 648s = 916170 line / sec
    • db files
    21G 2014-11-18 23:08 meta_c.db
    
  • search db(random)
    • os cache 삭제
    sudo sh -c "sync; echo 3 > /proc/sys/vm/drop_caches"
    
    • 시간 : 174s = 574 line / sec

Test in c with MAP_PRIVATE|MAP_POPULATE

  • 위에서 언급한것처럼 'mdb.c'의 'MAP_SHARED' 플래그를 수정해서 컴파일한 상태에서 테스트
  • make db : 차이없음
  • search db(random)
    • 시간 : 1s = 100000 line / sec
    • 로딩 시간 : 10m
    • 메모리 사용량
    21 giga
    
  • 시스템 memory가 충분하다면, 'mdb.c'를 수정해서 위와 같은 방법으로 사용하는 것도 좋을 것 같다. 로딩시간이 10분 정도라서 느리긴 하지만, 일단 한번 메모리에 로딩되면 LM을 사용하는 모듈 performance도 크게 향상될수 있기 때문이다. 자주 업데이트되는 자원도 아니고~
  • multi-processing
    • 별 문제없이 shared 영역이 공유되는 것을 확인
    24.0g 6.8g 6.8g D  7.9 14.3   0:04.34 search_lmdb
    24.0g 6.8g 6.8g D  7.6 14.3   0:04.30 search_lmdb
    ...
    

Etc

  • 기타 주의 사항
    • make_lmdb.py 등으로 db를 생성할 때, 기존 파일이 존재한다면 거기에 추가하게된다. 따라서, 미리 삭제를 해야함
    • map_size를 크게 잡는 경우 virtual memory 사이즈도 그만큼 잡힌다. 32bit machine에서는 2G를 넘지 않게 해야한다.
      • 하지만, 64bit에는 문제없음. VIRT가 크게 잡혀도 performance에 문제는 없음.

Code

Clone this wiki locally