You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@lucene.apache.org by Scott Ganyo <sc...@eTapestry.com> on 2002/04/02 22:28:53 UTC
Searcher/Reader/Writer Management
It seems that a lot of people run into the same set of problems with
maintaining readers, writers, and searchers in Lucene. Part of the problem
comes from the fact that delete() is on a reader and part of it comes from
the need to keep a reader or search open as long as possible to avoid
open/close overhead. Add to that the need for some applications (like mine)
to maintain multiple indexes and you have a pretty complex problem space.
Bottom line: Use of Lucene (especially in an online, interactive
application) doesn't seem as easy as it could be.
Anyway... I've attempted to address these issues in my application with a
control class. And, I'm submitting what I have to lucene dev for review...
and consideration for possible inclusion in the core lucene framework. I
tried to be fairly efficient and to handle all cases that I know about so
let me know if it fails in either case and what you think overall.
Thanks,
Scott
-------
import java.io.*;
import java.util.*;
import org.apache.lucene.index.*;
import org.apache.lucene.document.*;
import org.apache.lucene.search.*;
import org.apache.lucene.analysis.*;
/** Rules:
* Once created, searchers are valid until closed.
* If doing an update, searchers must not be created between close of the
reader
* that deletes and close of the writer that adds.
* Writers may be reused until Reader or Searcher is needed.
* Readers may be reused until Writer or Searcher is needed.
* Searchers may be reused until the index is changed.
* There may be only one of a Reader and/or Writer at a time.
* If you get it, release it.
*/
public class IndexAccessControl
{
public static final Analyzer LUCENE_ANALYZER = new LuceneAnalyzer();
private static final Map WRITER_PATHS = new HashMap(); // path ->
CheckoutInfo
private static final Map SEARCHER_PATHS = new HashMap(); // path ->
Searcher
private static final Map OLD_SEARCHERS = new HashMap(); // Searcher ->
CheckoutInfo
/** get for adding documents.
* blocks: readers until released
*/
public static IndexWriter getWriter(File path) throws IOException
{
IndexWriter writer = null;
String sync = path.getAbsolutePath().intern();
synchronized (sync) // sync on specific index
{
do
{
CheckoutInfo info = (CheckoutInfo)WRITER_PATHS.get(path);
if (info != null) // may already have a writer, use it
{
if (info.writer != null) // yup, have a writer
{
info.checkoutCount++;
writer = info.writer;
}
else // not a writer, it must be a reader, wait for it
to finish to try again
{
try
{
info.wait(); // wait for info to be released
}
catch (InterruptedException e)
{
// TODO: Will this ever happen?
e.printStackTrace();
return null;
}
}
}
else // no writer, create one
{
boolean missing = !path.exists();
if (missing) path.mkdir();
writer = new IndexWriter(path, LUCENE_ANALYZER,
/*create*/missing);
writer.mergeFactor = 2;
info = new CheckoutInfo(writer);
}
}
while (writer == null);
}
return writer;
}
public static void releaseWriter(File path, IndexWriter writer) throws
IOException
{
String sync = path.getAbsolutePath().intern();
synchronized (sync) // sync on specific index
{
CheckoutInfo info = (CheckoutInfo)WRITER_PATHS.get(path);
if (info != null && writer == info.writer) // writer was checked
out
{
if (info.checkoutCount > 1) // writer has other references
{
info.checkoutCount--;
writer = null; // avoid close()
}
else // last reference to writer
{
WRITER_PATHS.remove(path);
writer = info.writer;
info.notify(); // notify waiters to try again
}
}
}
// close the writer (unless it still has checkouts)
if (writer != null) writer.close();
}
/** get for search. */
public static Searcher getSearcher(File path) throws IOException
{
IndexSearcher is;
String sync = path.getAbsolutePath().intern();
synchronized (sync) // sync on specific index
{
CheckoutInfo info = (CheckoutInfo)SEARCHER_PATHS.get(path);
if (info == null || IndexReader.lastModified(path) >
info.creationTime)
{
// need new searcher
is = new IndexSearcher(IndexReader.open(path));
info = (CheckoutInfo)SEARCHER_PATHS.put(path, new
CheckoutInfo(is));
if (info != null)
{
if (info.checkoutCount > 1) // searcher has other
references
{
info.checkoutCount--;
OLD_SEARCHERS.put(info.searcher, info);
}
else // last reference to searcher
{
info.searcher.close();
}
}
}
else
{
// use existing searcher
is = info.searcher;
info.checkoutCount++;
}
}
return is;
}
public static void releaseSearcher(File path, Searcher searcher) throws
IOException
{
String sync = path.getAbsolutePath().intern();
synchronized (sync) // sync on specific index
{
CheckoutInfo info = (CheckoutInfo)SEARCHER_PATHS.get(path);
if (info == null || searcher != info.searcher) // this isn't the
info we're looking for
{
info = (CheckoutInfo)OLD_SEARCHERS.get(searcher);
}
if (info != null) // found a searcher
{
if (info.checkoutCount > 1) // searcher has other references
{
info.checkoutCount--;
}
else // last reference to searcher
{
info.searcher.close();
}
}
else // can't find searcher, just close it
{
searcher.close();
}
}
}
/** get for deleting documents.
* blocks: writers until released
*/
public static IndexReader getReader(File path) throws IOException
{
IndexReader reader = null;
String sync = path.getAbsolutePath().intern();
synchronized (sync) // sync on specific index
{
do
{
CheckoutInfo info = (CheckoutInfo)WRITER_PATHS.get(path);
if (info != null) // may already have a reader, use it
{
if (info.reader != null) // yup, have a reader
{
info.checkoutCount++;
reader = info.reader;
}
else // not a reader, it must be a writer, wait for it
to finish to try again
{
try
{
info.wait(); // wait for info to be released
}
catch (InterruptedException e)
{
// TODO: Will this ever happen?
e.printStackTrace();
return null;
}
}
}
else // no reader, create one
{
reader = IndexReader.open(path);
info = new CheckoutInfo(reader);
}
}
while (reader == null);
}
return reader;
}
public static void releaseReader(File path, IndexReader reader) throws
IOException
{
String sync = path.getAbsolutePath().intern();
synchronized (sync) // sync on specific index
{
CheckoutInfo info = (CheckoutInfo)WRITER_PATHS.get(path);
if (info != null && reader == info.reader) // reader was checked
out
{
if (info.checkoutCount > 1) // reader has other references
{
info.checkoutCount--;
reader = null; // avoid close()
}
else // last reference to reader
{
WRITER_PATHS.remove(path);
reader = info.reader;
info.notify(); // notify waiters to try again
}
}
}
// close the reader (unless it still has checkouts)
if (reader != null) reader.close();
}
/** used for updates to make sure nobody else grabs a writer or reader
between
* release and get operations.
*/
public static IndexWriter releaseReaderAndGetWriter(File path,
IndexReader reader) throws IOException
{
String sync = path.getAbsolutePath().intern();
synchronized (sync) // sync on specific index
{
releaseReader(path, reader);
return getWriter(path);
}
}
private static class CheckoutInfo
{
CheckoutInfo(IndexWriter writer)
{
this.writer = writer;
}
CheckoutInfo(IndexReader reader)
{
this.reader = reader;
}
CheckoutInfo(IndexSearcher searcher)
{
this.searcher = searcher;
this.creationTime = System.currentTimeMillis();
}
public IndexReader reader;
public IndexWriter writer;
public IndexSearcher searcher;
public int checkoutCount = 1;
public long creationTime;
}
/** no instances */
private IndexAccessControl() { }
}