~slub.team/goobi-indexserver/3.x

« back to all changes in this revision

Viewing changes to lucene/src/java/org/apache/lucene/index/LogMergePolicy.java

  • Committer: Sebastian Meyer
  • Date: 2012-08-03 09:12:40 UTC
  • Revision ID: sebastian.meyer@slub-dresden.de-20120803091240-x6861b0vabq1xror
Remove Lucene and Solr source code and add patches instead
Fix Bug #985487: Auto-suggestion for the search interface

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
package org.apache.lucene.index;
2
 
 
3
 
/**
4
 
 * Licensed to the Apache Software Foundation (ASF) under one or more
5
 
 * contributor license agreements.  See the NOTICE file distributed with
6
 
 * this work for additional information regarding copyright ownership.
7
 
 * The ASF licenses this file to You under the Apache License, Version 2.0
8
 
 * (the "License"); you may not use this file except in compliance with
9
 
 * the License.  You may obtain a copy of the License at
10
 
 *
11
 
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 
 *
13
 
 * Unless required by applicable law or agreed to in writing, software
14
 
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 
 * See the License for the specific language governing permissions and
17
 
 * limitations under the License.
18
 
 */
19
 
 
20
 
import java.io.IOException;
21
 
import java.util.ArrayList;
22
 
import java.util.Collection;
23
 
import java.util.List;
24
 
import java.util.Map;
25
 
 
26
 
/** <p>This class implements a {@link MergePolicy} that tries
27
 
 *  to merge segments into levels of exponentially
28
 
 *  increasing size, where each level has fewer segments than
29
 
 *  the value of the merge factor. Whenever extra segments
30
 
 *  (beyond the merge factor upper bound) are encountered,
31
 
 *  all segments within the level are merged. You can get or
32
 
 *  set the merge factor using {@link #getMergeFactor()} and
33
 
 *  {@link #setMergeFactor(int)} respectively.</p>
34
 
 *
35
 
 *  <p>This class is abstract and requires a subclass to
36
 
 *  define the {@link #size} method which specifies how a
37
 
 *  segment's size is determined.  {@link LogDocMergePolicy}
38
 
 *  is one subclass that measures size by document count in
39
 
 *  the segment.  {@link LogByteSizeMergePolicy} is another
40
 
 *  subclass that measures size as the total byte size of the
41
 
 *  file(s) for the segment.</p>
42
 
 */
43
 
 
44
 
public abstract class LogMergePolicy extends MergePolicy {
45
 
 
46
 
  /** Defines the allowed range of log(size) for each
47
 
   *  level.  A level is computed by taking the max segment
48
 
   *  log size, minus LEVEL_LOG_SPAN, and finding all
49
 
   *  segments falling within that range. */
50
 
  public static final double LEVEL_LOG_SPAN = 0.75;
51
 
 
52
 
  /** Default merge factor, which is how many segments are
53
 
   *  merged at a time */
54
 
  public static final int DEFAULT_MERGE_FACTOR = 10;
55
 
 
56
 
  /** Default maximum segment size.  A segment of this size
57
 
   *  or larger will never be merged.  @see setMaxMergeDocs */
58
 
  public static final int DEFAULT_MAX_MERGE_DOCS = Integer.MAX_VALUE;
59
 
 
60
 
  /** Default noCFSRatio.  If a merge's size is >= 10% of
61
 
   *  the index, then we disable compound file for it.
62
 
   *  @see #setNoCFSRatio */
63
 
  public static final double DEFAULT_NO_CFS_RATIO = 0.1;
64
 
 
65
 
  protected int mergeFactor = DEFAULT_MERGE_FACTOR;
66
 
 
67
 
  protected long minMergeSize;
68
 
  protected long maxMergeSize;
69
 
  // Although the core MPs set it explicitly, we must default in case someone
70
 
  // out there wrote his own LMP ...
71
 
  protected long maxMergeSizeForForcedMerge = Long.MAX_VALUE;
72
 
  protected int maxMergeDocs = DEFAULT_MAX_MERGE_DOCS;
73
 
 
74
 
  protected double noCFSRatio = DEFAULT_NO_CFS_RATIO;
75
 
 
76
 
  protected boolean calibrateSizeByDeletes = true;
77
 
  
78
 
  protected boolean useCompoundFile = true;
79
 
 
80
 
  public LogMergePolicy() {
81
 
    super();
82
 
  }
83
 
 
84
 
  protected boolean verbose() {
85
 
    IndexWriter w = writer.get();
86
 
    return w != null && w.verbose();
87
 
  }
88
 
 
89
 
  /** @see #setNoCFSRatio */
90
 
  public double getNoCFSRatio() {
91
 
    return noCFSRatio;
92
 
  }
93
 
 
94
 
  /** If a merged segment will be more than this percentage
95
 
   *  of the total size of the index, leave the segment as
96
 
   *  non-compound file even if compound file is enabled.
97
 
   *  Set to 1.0 to always use CFS regardless of merge
98
 
   *  size. */
99
 
  public void setNoCFSRatio(double noCFSRatio) {
100
 
    if (noCFSRatio < 0.0 || noCFSRatio > 1.0) {
101
 
      throw new IllegalArgumentException("noCFSRatio must be 0.0 to 1.0 inclusive; got " + noCFSRatio);
102
 
    }
103
 
    this.noCFSRatio = noCFSRatio;
104
 
  }
105
 
  
106
 
  protected void message(String message) {
107
 
    if (verbose())
108
 
      writer.get().message("LMP: " + message);
109
 
  }
110
 
 
111
 
  /** <p>Returns the number of segments that are merged at
112
 
   * once and also controls the total number of segments
113
 
   * allowed to accumulate in the index.</p> */
114
 
  public int getMergeFactor() {
115
 
    return mergeFactor;
116
 
  }
117
 
 
118
 
  /** Determines how often segment indices are merged by
119
 
   * addDocument().  With smaller values, less RAM is used
120
 
   * while indexing, and searches are
121
 
   * faster, but indexing speed is slower.  With larger
122
 
   * values, more RAM is used during indexing, and while
123
 
   * searches is slower, indexing is
124
 
   * faster.  Thus larger values (> 10) are best for batch
125
 
   * index creation, and smaller values (< 10) for indices
126
 
   * that are interactively maintained. */
127
 
  public void setMergeFactor(int mergeFactor) {
128
 
    if (mergeFactor < 2)
129
 
      throw new IllegalArgumentException("mergeFactor cannot be less than 2");
130
 
    this.mergeFactor = mergeFactor;
131
 
  }
132
 
 
133
 
  // Javadoc inherited
134
 
  @Override
135
 
  public boolean useCompoundFile(SegmentInfos infos, SegmentInfo mergedInfo) throws IOException {
136
 
    final boolean doCFS;
137
 
 
138
 
    if (!useCompoundFile) {
139
 
      doCFS = false;
140
 
    } else if (noCFSRatio == 1.0) {
141
 
      doCFS = true;
142
 
    } else {
143
 
      long totalSize = 0;
144
 
      for (SegmentInfo info : infos)
145
 
        totalSize += size(info);
146
 
 
147
 
      doCFS = size(mergedInfo) <= noCFSRatio * totalSize;
148
 
    }
149
 
    return doCFS;
150
 
  }
151
 
 
152
 
  /** Sets whether compound file format should be used for
153
 
   *  newly flushed and newly merged segments. */
154
 
  public void setUseCompoundFile(boolean useCompoundFile) {
155
 
    this.useCompoundFile = useCompoundFile;
156
 
  }
157
 
 
158
 
  /** Returns true if newly flushed and newly merge segments
159
 
   *  are written in compound file format. @see
160
 
   *  #setUseCompoundFile */
161
 
  public boolean getUseCompoundFile() {
162
 
    return useCompoundFile;
163
 
  }
164
 
 
165
 
  /** Sets whether the segment size should be calibrated by
166
 
   *  the number of deletes when choosing segments for merge. */
167
 
  public void setCalibrateSizeByDeletes(boolean calibrateSizeByDeletes) {
168
 
    this.calibrateSizeByDeletes = calibrateSizeByDeletes;
169
 
  }
170
 
 
171
 
  /** Returns true if the segment size should be calibrated 
172
 
   *  by the number of deletes when choosing segments for merge. */
173
 
  public boolean getCalibrateSizeByDeletes() {
174
 
    return calibrateSizeByDeletes;
175
 
  }
176
 
 
177
 
  @Override
178
 
  public void close() {}
179
 
 
180
 
  abstract protected long size(SegmentInfo info) throws IOException;
181
 
 
182
 
  protected long sizeDocs(SegmentInfo info) throws IOException {
183
 
    if (calibrateSizeByDeletes) {
184
 
      int delCount = writer.get().numDeletedDocs(info);
185
 
      assert delCount <= info.docCount;
186
 
      return (info.docCount - (long)delCount);
187
 
    } else {
188
 
      return info.docCount;
189
 
    }
190
 
  }
191
 
  
192
 
  protected long sizeBytes(SegmentInfo info) throws IOException {
193
 
    long byteSize = info.sizeInBytes(true);
194
 
    if (calibrateSizeByDeletes) {
195
 
      int delCount = writer.get().numDeletedDocs(info);
196
 
      double delRatio = (info.docCount <= 0 ? 0.0f : ((float)delCount / (float)info.docCount));
197
 
      assert delRatio <= 1.0;
198
 
      return (info.docCount <= 0 ?  byteSize : (long)(byteSize * (1.0 - delRatio)));
199
 
    } else {
200
 
      return byteSize;
201
 
    }
202
 
  }
203
 
  
204
 
  protected boolean isMerged(SegmentInfos infos, int maxNumSegments, Map<SegmentInfo,Boolean> segmentsToMerge) throws IOException {
205
 
    final int numSegments = infos.size();
206
 
    int numToMerge = 0;
207
 
    SegmentInfo mergeInfo = null;
208
 
    boolean segmentIsOriginal = false;
209
 
    for(int i=0;i<numSegments && numToMerge <= maxNumSegments;i++) {
210
 
      final SegmentInfo info = infos.info(i);
211
 
      final Boolean isOriginal = segmentsToMerge.get(info);
212
 
      if (isOriginal != null) {
213
 
        segmentIsOriginal = isOriginal;
214
 
        numToMerge++;
215
 
        mergeInfo = info;
216
 
      }
217
 
    }
218
 
 
219
 
    return numToMerge <= maxNumSegments &&
220
 
      (numToMerge != 1 || !segmentIsOriginal || isMerged(mergeInfo));
221
 
  }
222
 
 
223
 
  /** Returns true if this single info is already fully merged (has no
224
 
   *  pending norms or deletes, is in the same dir as the
225
 
   *  writer, and matches the current compound file setting */
226
 
  protected boolean isMerged(SegmentInfo info)
227
 
    throws IOException {
228
 
    IndexWriter w = writer.get();
229
 
    assert w != null;
230
 
    boolean hasDeletions = w.numDeletedDocs(info) > 0;
231
 
    return !hasDeletions &&
232
 
      !info.hasSeparateNorms() &&
233
 
      info.dir == w.getDirectory() &&
234
 
      (info.getUseCompoundFile() == useCompoundFile || noCFSRatio < 1.0);
235
 
  }
236
 
 
237
 
  /**
238
 
   * Returns the merges necessary to merge the index, taking the max merge
239
 
   * size or max merge docs into consideration. This method attempts to respect
240
 
   * the {@code maxNumSegments} parameter, however it might be, due to size
241
 
   * constraints, that more than that number of segments will remain in the
242
 
   * index. Also, this method does not guarantee that exactly {@code
243
 
   * maxNumSegments} will remain, but &lt;= that number.
244
 
   */
245
 
  private MergeSpecification findForcedMergesSizeLimit(
246
 
      SegmentInfos infos, int maxNumSegments, int last) throws IOException {
247
 
    MergeSpecification spec = new MergeSpecification();
248
 
    final List<SegmentInfo> segments = infos.asList();
249
 
 
250
 
    int start = last - 1;
251
 
    while (start >= 0) {
252
 
      SegmentInfo info = infos.info(start);
253
 
      if (size(info) > maxMergeSizeForForcedMerge || sizeDocs(info) > maxMergeDocs) {
254
 
        if (verbose()) {
255
 
          message("findForcedMergesSizeLimit: skip segment=" + info + ": size is > maxMergeSize (" + maxMergeSizeForForcedMerge + ") or sizeDocs is > maxMergeDocs (" + maxMergeDocs + ")");
256
 
        }
257
 
        // need to skip that segment + add a merge for the 'right' segments,
258
 
        // unless there is only 1 which is merged.
259
 
        if (last - start - 1 > 1 || (start != last - 1 && !isMerged(infos.info(start + 1)))) {
260
 
          // there is more than 1 segment to the right of
261
 
          // this one, or a mergeable single segment.
262
 
          spec.add(new OneMerge(segments.subList(start + 1, last)));
263
 
        }
264
 
        last = start;
265
 
      } else if (last - start == mergeFactor) {
266
 
        // mergeFactor eligible segments were found, add them as a merge.
267
 
        spec.add(new OneMerge(segments.subList(start, last)));
268
 
        last = start;
269
 
      }
270
 
      --start;
271
 
    }
272
 
 
273
 
    // Add any left-over segments, unless there is just 1
274
 
    // already fully merged
275
 
    if (last > 0 && (++start + 1 < last || !isMerged(infos.info(start)))) {
276
 
      spec.add(new OneMerge(segments.subList(start, last)));
277
 
    }
278
 
 
279
 
    return spec.merges.size() == 0 ? null : spec;
280
 
  }
281
 
  
282
 
  /**
283
 
   * Returns the merges necessary to forceMerge the index. This method constraints
284
 
   * the returned merges only by the {@code maxNumSegments} parameter, and
285
 
   * guaranteed that exactly that number of segments will remain in the index.
286
 
   */
287
 
  private MergeSpecification findForcedMergesMaxNumSegments(SegmentInfos infos, int maxNumSegments, int last) throws IOException {
288
 
    MergeSpecification spec = new MergeSpecification();
289
 
    final List<SegmentInfo> segments = infos.asList();
290
 
 
291
 
    // First, enroll all "full" merges (size
292
 
    // mergeFactor) to potentially be run concurrently:
293
 
    while (last - maxNumSegments + 1 >= mergeFactor) {
294
 
      spec.add(new OneMerge(segments.subList(last - mergeFactor, last)));
295
 
      last -= mergeFactor;
296
 
    }
297
 
 
298
 
    // Only if there are no full merges pending do we
299
 
    // add a final partial (< mergeFactor segments) merge:
300
 
    if (0 == spec.merges.size()) {
301
 
      if (maxNumSegments == 1) {
302
 
 
303
 
        // Since we must merge down to 1 segment, the
304
 
        // choice is simple:
305
 
        if (last > 1 || !isMerged(infos.info(0))) {
306
 
          spec.add(new OneMerge(segments.subList(0, last)));
307
 
        }
308
 
      } else if (last > maxNumSegments) {
309
 
 
310
 
        // Take care to pick a partial merge that is
311
 
        // least cost, but does not make the index too
312
 
        // lopsided.  If we always just picked the
313
 
        // partial tail then we could produce a highly
314
 
        // lopsided index over time:
315
 
 
316
 
        // We must merge this many segments to leave
317
 
        // maxNumSegments in the index (from when
318
 
        // forceMerge was first kicked off):
319
 
        final int finalMergeSize = last - maxNumSegments + 1;
320
 
 
321
 
        // Consider all possible starting points:
322
 
        long bestSize = 0;
323
 
        int bestStart = 0;
324
 
 
325
 
        for(int i=0;i<last-finalMergeSize+1;i++) {
326
 
          long sumSize = 0;
327
 
          for(int j=0;j<finalMergeSize;j++)
328
 
            sumSize += size(infos.info(j+i));
329
 
          if (i == 0 || (sumSize < 2*size(infos.info(i-1)) && sumSize < bestSize)) {
330
 
            bestStart = i;
331
 
            bestSize = sumSize;
332
 
          }
333
 
        }
334
 
 
335
 
        spec.add(new OneMerge(segments.subList(bestStart, bestStart + finalMergeSize)));
336
 
      }
337
 
    }
338
 
    return spec.merges.size() == 0 ? null : spec;
339
 
  }
340
 
  
341
 
  /** Returns the merges necessary to merge the index down
342
 
   *  to a specified number of segments.
343
 
   *  This respects the {@link #maxMergeSizeForForcedMerge} setting.
344
 
   *  By default, and assuming {@code maxNumSegments=1}, only
345
 
   *  one segment will be left in the index, where that segment
346
 
   *  has no deletions pending nor separate norms, and it is in
347
 
   *  compound file format if the current useCompoundFile
348
 
   *  setting is true.  This method returns multiple merges
349
 
   *  (mergeFactor at a time) so the {@link MergeScheduler}
350
 
   *  in use may make use of concurrency. */
351
 
  @Override
352
 
  public MergeSpecification findForcedMerges(SegmentInfos infos,
353
 
            int maxNumSegments, Map<SegmentInfo,Boolean> segmentsToMerge) throws IOException {
354
 
 
355
 
    assert maxNumSegments > 0;
356
 
    if (verbose()) {
357
 
      message("findForcedMerges: maxNumSegs=" + maxNumSegments + " segsToMerge="+ segmentsToMerge);
358
 
    }
359
 
 
360
 
    // If the segments are already merged (e.g. there's only 1 segment), or
361
 
    // there are <maxNumSegements:.
362
 
    if (isMerged(infos, maxNumSegments, segmentsToMerge)) {
363
 
      if (verbose()) {
364
 
        message("already merged; skip");
365
 
      }
366
 
      return null;
367
 
    }
368
 
 
369
 
    // Find the newest (rightmost) segment that needs to
370
 
    // be merged (other segments may have been flushed
371
 
    // since merging started):
372
 
    int last = infos.size();
373
 
    while (last > 0) {
374
 
      final SegmentInfo info = infos.info(--last);
375
 
      if (segmentsToMerge.get(info) != null) {
376
 
        last++;
377
 
        break;
378
 
      }
379
 
    }
380
 
 
381
 
    if (last == 0) return null;
382
 
    
383
 
    // There is only one segment already, and it is merged
384
 
    if (maxNumSegments == 1 && last == 1 && isMerged(infos.info(0))) {
385
 
      if (verbose()) {
386
 
        message("already 1 seg; skip");
387
 
      }
388
 
      return null;
389
 
    }
390
 
 
391
 
    // Check if there are any segments above the threshold
392
 
    boolean anyTooLarge = false;
393
 
    for (int i = 0; i < last; i++) {
394
 
      SegmentInfo info = infos.info(i);
395
 
      if (size(info) > maxMergeSizeForForcedMerge || sizeDocs(info) > maxMergeDocs) {
396
 
        anyTooLarge = true;
397
 
        break;
398
 
      }
399
 
    }
400
 
 
401
 
    if (anyTooLarge) {
402
 
      return findForcedMergesSizeLimit(infos, maxNumSegments, last);
403
 
    } else {
404
 
      return findForcedMergesMaxNumSegments(infos, maxNumSegments, last);
405
 
    }
406
 
  }
407
 
 
408
 
  /**
409
 
   * Finds merges necessary to force-merge all deletes from the
410
 
   * index.  We simply merge adjacent segments that have
411
 
   * deletes, up to mergeFactor at a time.
412
 
   */ 
413
 
  @Override
414
 
  public MergeSpecification findForcedDeletesMerges(SegmentInfos segmentInfos)
415
 
      throws CorruptIndexException, IOException {
416
 
    final List<SegmentInfo> segments = segmentInfos.asList();
417
 
    final int numSegments = segments.size();
418
 
 
419
 
    if (verbose())
420
 
      message("findForcedDeleteMerges: " + numSegments + " segments");
421
 
 
422
 
    MergeSpecification spec = new MergeSpecification();
423
 
    int firstSegmentWithDeletions = -1;
424
 
    IndexWriter w = writer.get();
425
 
    assert w != null;
426
 
    for(int i=0;i<numSegments;i++) {
427
 
      final SegmentInfo info = segmentInfos.info(i);
428
 
      int delCount = w.numDeletedDocs(info);
429
 
      if (delCount > 0) {
430
 
        if (verbose())
431
 
          message("  segment " + info.name + " has deletions");
432
 
        if (firstSegmentWithDeletions == -1)
433
 
          firstSegmentWithDeletions = i;
434
 
        else if (i - firstSegmentWithDeletions == mergeFactor) {
435
 
          // We've seen mergeFactor segments in a row with
436
 
          // deletions, so force a merge now:
437
 
          if (verbose())
438
 
            message("  add merge " + firstSegmentWithDeletions + " to " + (i-1) + " inclusive");
439
 
          spec.add(new OneMerge(segments.subList(firstSegmentWithDeletions, i)));
440
 
          firstSegmentWithDeletions = i;
441
 
        }
442
 
      } else if (firstSegmentWithDeletions != -1) {
443
 
        // End of a sequence of segments with deletions, so,
444
 
        // merge those past segments even if it's fewer than
445
 
        // mergeFactor segments
446
 
        if (verbose())
447
 
          message("  add merge " + firstSegmentWithDeletions + " to " + (i-1) + " inclusive");
448
 
        spec.add(new OneMerge(segments.subList(firstSegmentWithDeletions, i)));
449
 
        firstSegmentWithDeletions = -1;
450
 
      }
451
 
    }
452
 
 
453
 
    if (firstSegmentWithDeletions != -1) {
454
 
      if (verbose())
455
 
        message("  add merge " + firstSegmentWithDeletions + " to " + (numSegments-1) + " inclusive");
456
 
      spec.add(new OneMerge(segments.subList(firstSegmentWithDeletions, numSegments)));
457
 
    }
458
 
 
459
 
    return spec;
460
 
  }
461
 
 
462
 
  private static class SegmentInfoAndLevel implements Comparable<SegmentInfoAndLevel> {
463
 
    SegmentInfo info;
464
 
    float level;
465
 
    int index;
466
 
    
467
 
    public SegmentInfoAndLevel(SegmentInfo info, float level, int index) {
468
 
      this.info = info;
469
 
      this.level = level;
470
 
      this.index = index;
471
 
    }
472
 
 
473
 
    // Sorts largest to smallest
474
 
    public int compareTo(SegmentInfoAndLevel other) {
475
 
      if (level < other.level)
476
 
        return 1;
477
 
      else if (level > other.level)
478
 
        return -1;
479
 
      else
480
 
        return 0;
481
 
    }
482
 
  }
483
 
 
484
 
  /** Checks if any merges are now necessary and returns a
485
 
   *  {@link MergePolicy.MergeSpecification} if so.  A merge
486
 
   *  is necessary when there are more than {@link
487
 
   *  #setMergeFactor} segments at a given level.  When
488
 
   *  multiple levels have too many segments, this method
489
 
   *  will return multiple merges, allowing the {@link
490
 
   *  MergeScheduler} to use concurrency. */
491
 
  @Override
492
 
  public MergeSpecification findMerges(SegmentInfos infos) throws IOException {
493
 
 
494
 
    final int numSegments = infos.size();
495
 
    if (verbose())
496
 
      message("findMerges: " + numSegments + " segments");
497
 
 
498
 
    // Compute levels, which is just log (base mergeFactor)
499
 
    // of the size of each segment
500
 
    final List<SegmentInfoAndLevel> levels = new ArrayList<SegmentInfoAndLevel>();
501
 
    final float norm = (float) Math.log(mergeFactor);
502
 
 
503
 
    final Collection<SegmentInfo> mergingSegments = writer.get().getMergingSegments();
504
 
 
505
 
    for(int i=0;i<numSegments;i++) {
506
 
      final SegmentInfo info = infos.info(i);
507
 
      long size = size(info);
508
 
 
509
 
      // Floor tiny segments
510
 
      if (size < 1) {
511
 
        size = 1;
512
 
      }
513
 
 
514
 
      final SegmentInfoAndLevel infoLevel = new SegmentInfoAndLevel(info, (float) Math.log(size)/norm, i);
515
 
      levels.add(infoLevel);
516
 
 
517
 
      if (verbose()) {
518
 
        final long segBytes = sizeBytes(info);
519
 
        String extra = mergingSegments.contains(info) ? " [merging]" : "";
520
 
        if (size >= maxMergeSize) {
521
 
          extra += " [skip: too large]";
522
 
        }
523
 
        message("seg=" + writer.get().segString(info) + " level=" + infoLevel.level + " size=" + String.format("%.3f MB", segBytes/1024/1024.) + extra);
524
 
      }
525
 
    }
526
 
 
527
 
    final float levelFloor;
528
 
    if (minMergeSize <= 0)
529
 
      levelFloor = (float) 0.0;
530
 
    else
531
 
      levelFloor = (float) (Math.log(minMergeSize)/norm);
532
 
 
533
 
    // Now, we quantize the log values into levels.  The
534
 
    // first level is any segment whose log size is within
535
 
    // LEVEL_LOG_SPAN of the max size, or, who has such as
536
 
    // segment "to the right".  Then, we find the max of all
537
 
    // other segments and use that to define the next level
538
 
    // segment, etc.
539
 
 
540
 
    MergeSpecification spec = null;
541
 
 
542
 
    final int numMergeableSegments = levels.size();
543
 
 
544
 
    int start = 0;
545
 
    while(start < numMergeableSegments) {
546
 
 
547
 
      // Find max level of all segments not already
548
 
      // quantized.
549
 
      float maxLevel = levels.get(start).level;
550
 
      for(int i=1+start;i<numMergeableSegments;i++) {
551
 
        final float level = levels.get(i).level;
552
 
        if (level > maxLevel)
553
 
          maxLevel = level;
554
 
      }
555
 
 
556
 
      // Now search backwards for the rightmost segment that
557
 
      // falls into this level:
558
 
      float levelBottom;
559
 
      if (maxLevel <= levelFloor)
560
 
        // All remaining segments fall into the min level
561
 
        levelBottom = -1.0F;
562
 
      else {
563
 
        levelBottom = (float) (maxLevel - LEVEL_LOG_SPAN);
564
 
 
565
 
        // Force a boundary at the level floor
566
 
        if (levelBottom < levelFloor && maxLevel >= levelFloor)
567
 
          levelBottom = levelFloor;
568
 
      }
569
 
 
570
 
      int upto = numMergeableSegments-1;
571
 
      while(upto >= start) {
572
 
        if (levels.get(upto).level >= levelBottom) {
573
 
          break;
574
 
        }
575
 
        upto--;
576
 
      }
577
 
      if (verbose())
578
 
        message("  level " + levelBottom + " to " + maxLevel + ": " + (1+upto-start) + " segments");
579
 
 
580
 
      // Finally, record all merges that are viable at this level:
581
 
      int end = start + mergeFactor;
582
 
      while(end <= 1+upto) {
583
 
        boolean anyTooLarge = false;
584
 
        boolean anyMerging = false;
585
 
        for(int i=start;i<end;i++) {
586
 
          final SegmentInfo info = levels.get(i).info;
587
 
          anyTooLarge |= (size(info) >= maxMergeSize || sizeDocs(info) >= maxMergeDocs);
588
 
          if (mergingSegments.contains(info)) {
589
 
            anyMerging = true;
590
 
            break;
591
 
          }
592
 
        }
593
 
 
594
 
        if (anyMerging) {
595
 
          // skip
596
 
        } else if (!anyTooLarge) {
597
 
          if (spec == null)
598
 
            spec = new MergeSpecification();
599
 
          final List<SegmentInfo> mergeInfos = new ArrayList<SegmentInfo>();
600
 
          for(int i=start;i<end;i++) {
601
 
            mergeInfos.add(levels.get(i).info);
602
 
            assert infos.contains(levels.get(i).info);
603
 
          }
604
 
          if (verbose()) {
605
 
            message("  add merge=" + writer.get().segString(mergeInfos) + " start=" + start + " end=" + end);
606
 
          }
607
 
          spec.add(new OneMerge(mergeInfos));
608
 
        } else if (verbose()) {
609
 
          message("    " + start + " to " + end + ": contains segment over maxMergeSize or maxMergeDocs; skipping");
610
 
        }
611
 
 
612
 
        start = end;
613
 
        end = start + mergeFactor;
614
 
      }
615
 
 
616
 
      start = 1+upto;
617
 
    }
618
 
 
619
 
    return spec;
620
 
  }
621
 
 
622
 
  /** <p>Determines the largest segment (measured by
623
 
   * document count) that may be merged with other segments.
624
 
   * Small values (e.g., less than 10,000) are best for
625
 
   * interactive indexing, as this limits the length of
626
 
   * pauses while indexing to a few seconds.  Larger values
627
 
   * are best for batched indexing and speedier
628
 
   * searches.</p>
629
 
   *
630
 
   * <p>The default value is {@link Integer#MAX_VALUE}.</p>
631
 
   *
632
 
   * <p>The default merge policy ({@link
633
 
   * LogByteSizeMergePolicy}) also allows you to set this
634
 
   * limit by net size (in MB) of the segment, using {@link
635
 
   * LogByteSizeMergePolicy#setMaxMergeMB}.</p>
636
 
   */
637
 
  public void setMaxMergeDocs(int maxMergeDocs) {
638
 
    this.maxMergeDocs = maxMergeDocs;
639
 
  }
640
 
 
641
 
  /** Returns the largest segment (measured by document
642
 
   *  count) that may be merged with other segments.
643
 
   *  @see #setMaxMergeDocs */
644
 
  public int getMaxMergeDocs() {
645
 
    return maxMergeDocs;
646
 
  }
647
 
 
648
 
  @Override
649
 
  public String toString() {
650
 
    StringBuilder sb = new StringBuilder("[" + getClass().getSimpleName() + ": ");
651
 
    sb.append("minMergeSize=").append(minMergeSize).append(", ");
652
 
    sb.append("mergeFactor=").append(mergeFactor).append(", ");
653
 
    sb.append("maxMergeSize=").append(maxMergeSize).append(", ");
654
 
    sb.append("maxMergeSizeForForcedMerge=").append(maxMergeSizeForForcedMerge).append(", ");
655
 
    sb.append("calibrateSizeByDeletes=").append(calibrateSizeByDeletes).append(", ");
656
 
    sb.append("maxMergeDocs=").append(maxMergeDocs).append(", ");
657
 
    sb.append("useCompoundFile=").append(useCompoundFile).append(", ");
658
 
    sb.append("noCFSRatio=").append(noCFSRatio);
659
 
    sb.append("]");
660
 
    return sb.toString();
661
 
  }
662
 
  
663
 
}