2012-07-20

Lock in Lucene 3.6.0

Lucene 更新索引檔的過程:

  1. IndexWriter.addDocument() 或 IndexWriter.deleteDocument()
  2. Flush Buffered documents - 達到門檻值才會觸發,或者 IndexWriter.commit() 會觸發 flush,或者 IndexWriter.close() 會觸發 IndexWriter.commit(),而 IndexWriter.commit() 再觸發 flush。
  3. IndexWriter.prepareCommit()
  4. IndexWriter.commit() 或 IndexWriter.rollback()
  5. IndexWriter.close()
一個 IndexWriter 與 N 個 IndexReader?

呼叫 new IndexWriter() 之後,就會在索引檔目錄下產生 write.lock,用來標示索引檔「維護中」,他人請勿插手,但是可以查詢;也就是說,同一份索引檔同時只能由一個 IndexWriter 開啟,但是唯讀的 IndexReader 不受限制,write.lock 直到 IndexWriter commit 或 close 之後才會消失。

唯讀的 IndexReader 可以同時開啟索引檔,不管 IndexReader 是不是在同一個 JVM 或同一部電腦,但是考量效能,同一個 JVM 還是共用同一個 IndexReader 才是最佳實做。

但不是說從系統一啟動就開啟 IndexReader,然後一路用下去,這是有問題的,因為當 IndexReader 開啟以後,之後 IndexWriter 對索引檔的變動,在變動之前開啟的 IndexReader 是不會知道的,也就是說 IndexReader 會 cache 開啟當下的索引檔 IndexReader 若要讀到最新版的索引檔,得在 IndexWriter commit(或 close)之後,重新開啟 IndexReader 才行。

IndexWrtier 與 IndexReader 都是 thread-safe,所以多個執行緒共用 IndexWriter 與 IndexReader 是沒有問題的。

可以用 IndexWriter.isLock(directory) 知道索引檔是否被開啟了。

Debug Lucene

Lucene 提供相當詳細的 debug 訊息,只要設定 IndexWriter.setInfoStream(System.out) 就可以得到非常詳細的資訊。
IFD [Fri Jul 20 13:50:42 CST 2012; main]: setInfoStream deletionPolicy=org.apache.lucene.index.KeepOnlyLastCommitDeletionPolicy@9875d1
IW 0 [Fri Jul 20 13:50:42 CST 2012; main]: 
dir=org.apache.lucene.store.RAMDirectory@165b7e lockFactory=org.apache.lucene.store.SingleInstanceLockFactory@1d0d124
index=
version=3.6.0 1310449 - rmuir - 2012-04-06 11:31:16
matchVersion=LUCENE_36
analyzer=org.apache.lucene.analysis.standard.StandardAnalyzer
delPolicy=org.apache.lucene.index.KeepOnlyLastCommitDeletionPolicy
commit=null
openMode=CREATE
similarity=org.apache.lucene.search.DefaultSimilarity
termIndexInterval=128
mergeScheduler=org.apache.lucene.index.ConcurrentMergeScheduler
default WRITE_LOCK_TIMEOUT=1000
writeLockTimeout=1000
maxBufferedDeleteTerms=-1
ramBufferSizeMB=16.0
maxBufferedDocs=-1
mergedSegmentWarmer=null
mergePolicy=[TieredMergePolicy: maxMergeAtOnce=10, maxMergeAtOnceExplicit=30, maxMergedSegmentMB=5120.0, floorSegmentMB=2.0, forceMergeDeletesPctAllowed=10.0, segmentsPerTier=10.0, useCompoundFile=true, noCFSRatio=0.1
maxThreadStates=8
readerPooling=false
readerTermsIndexDivisor=1
包括所有的設定值,如deletionPolicy、Directory、Version、Analyzer、openMode、Similarity、mergeScheduler 等。

IndexReader 也可以修改索引檔?

是的,IndexReader 也可以修改索引檔(刪除索引檔或者修改 Norms),在這種情況下,IndexReader 也是一種 writer,必須拿到 write.lock 才能執行修改。

那為什麼要用 IndexReader 取代 IndexWriter 執行刪除文件呢?IndexReader 可以用 Document numer 執行刪除,也就是 ScoreDoc.doc 回傳的數值,可以在查詢得到 Documnet 後,直接刪除,這是使用 IndexReader 執行刪除最大的誘因。


但是 IndexReader 的修改索引檔功能在 4.0 以後就會被移除了。


在 IndexWriter.commit() 之前還有個 Buffer 機制


在呼叫 IndexWriter.addDocument() 或 deleteDocument() 之後,Lucene 並不會立即將修改同步到索引檔,會先暫存(Buffer)在記憶體裡,等到達到設定的門檻,才會寫到索引檔裡,但「並未寫死」,還得經過呼叫 IndexWriter.commit() 或 IndexWriter.close() 之後,才會真的更新到索引檔裡,這時開啟的 IndexReader 才會看到更新後的結果,這個 Buffer 的機制主要在減少硬碟的存取。


上述的門檻有三個,任一個達到即會觸動 flush,Lucene 預設只有設定 16MB 的限制,沒有另外限制文件門檻或刪除門檻,若要取消門檻,可以將其設為 IndexWriterConfig.DISABLE_AUTO_FLUSH
  private IndexWriter createWriter() throws CorruptIndexException,
      LockObtainFailedException, IOException {
    IndexWriterConfig config = new IndexWriterConfig(CrudTestCase.VERSION,
        new StandardAnalyzer(CrudTestCase.VERSION));
    config.setOpenMode(OpenMode.CREATE);
    config.setRAMBufferSizeMB(IndexWriterConfig.DISABLE_AUTO_FLUSH);
    config.setMaxBufferedDocs(IndexWriterConfig.DISABLE_AUTO_FLUSH);
    config.setMaxBufferedDeleteTerms(IndexWriterConfig.DISABLE_AUTO_FLUSH);
    return new IndexWriter(this.directory, config);
  }

與資料庫整合

Lucene 常用來取代資料庫的查詢功能,所以資料還是存在資料庫,然後將需要查詢的資料在 Lucene 建立索引,並將 table id 存到 Lucene 裡,供 Lucene 查詢取得 Document 之後可以對應到資料庫裡原始的資料。

除了要查詢的欄位得建到索引裡,以及 primary key 用來稍後取回原始資料外,一般用在列表頁呈現的欄位也會存到索引檔裡,方便 Lucene 的查詢結果就可以應付列表的顯示,等到需要單筆資料得所有欄位時,再拿 PK 到資料庫裡找。


為了與資料庫做有效的整合,Lucene 提供了一些 method:

  • IndexWriter.prepareCommit()
  • IndexWriter.commit()
  • IndexWriter.rollback()

需要解釋的是 prepareCommit(),這是用來確定 buffer 裡的索引可以更新到索引檔裡不會出錯,在確定這一點後,就可已將資料先寫到資料庫,然後再呼叫 IndexWriter.commit(),或者寫到資料庫失敗時,可以呼叫 IndexWriter.rollback() 以清空 buffer 的索引資料。

---

沒有留言:

張貼留言