add --shared
[pylucene.git] / lucene-java-3.4.0 / lucene / src / java / org / apache / lucene / index / TieredMergePolicy.java
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.Map;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.HashSet;
25 import java.util.Comparator;
26 import java.util.List;
27 import java.util.ArrayList;
28
29 /**
30  *  Merges segments of approximately equal size, subject to
31  *  an allowed number of segments per tier.  This is similar
32  *  to {@link LogByteSizeMergePolicy}, except this merge
33  *  policy is able to merge non-adjacent segment, and
34  *  separates how many segments are merged at once ({@link
35  *  #setMaxMergeAtOnce}) from how many segments are allowed
36  *  per tier ({@link #setSegmentsPerTier}).  This merge
37  *  policy also does not over-merge (ie, cascade merges). 
38  *
39  *  <p>For normal merging, this policy first computes a
40  *  "budget" of how many segments are allowed by be in the
41  *  index.  If the index is over-budget, then the policy
42  *  sorts segments by decresing size (pro-rating by percent
43  *  deletes), and then finds the least-cost merge.  Merge
44  *  cost is measured by a combination of the "skew" of the
45  *  merge (size of largest seg divided by smallest seg),
46  *  total merge size and pct deletes reclaimed,
47  *  so that merges with lower skew, smaller size
48  *  and those reclaiming more deletes, are
49  *  favored.
50  *
51  *  <p>If a merge will produce a segment that's larger than
52  *  {@link #setMaxMergedSegmentMB}, then the policy will
53  *  merge fewer segments (down to 1 at once, if that one has
54  *  deletions) to keep the segment size under budget.
55  *      
56  *  <p<b>NOTE</b>: this policy freely merges non-adjacent
57  *  segments; if this is a problem, use {@link
58  *  LogMergePolicy}.
59  *
60  *  <p><b>NOTE</b>: This policy always merges by byte size
61  *  of the segments, always pro-rates by percent deletes,
62  *  and does not apply any maximum segment size during
63  *  optimize (unlike {@link LogByteSizeMergePolicy}.
64  *
65  *  @lucene.experimental
66  */
67
68 // TODO
69 //   - we could try to take into account whether a large
70 //     merge is already running (under CMS) and then bias
71 //     ourselves towards picking smaller merges if so (or,
72 //     maybe CMS should do so)
73
74 public class TieredMergePolicy extends MergePolicy {
75
76   private int maxMergeAtOnce = 10;
77   private long maxMergedSegmentBytes = 5*1024*1024*1024L;
78   private int maxMergeAtOnceExplicit = 30;
79
80   private long floorSegmentBytes = 2*1024*1024L;
81   private double segsPerTier = 10.0;
82   private double expungeDeletesPctAllowed = 10.0;
83   private boolean useCompoundFile = true;
84   private double noCFSRatio = 0.1;
85   private double reclaimDeletesWeight = 2.0;
86
87   /** Maximum number of segments to be merged at a time
88    *  during "normal" merging.  For explicit merging (eg,
89    *  optimize or expungeDeletes was called), see {@link
90    *  #setMaxMergeAtOnceExplicit}.  Default is 10. */
91   public TieredMergePolicy setMaxMergeAtOnce(int v) {
92     if (v < 2) {
93       throw new IllegalArgumentException("maxMergeAtOnce must be > 1 (got " + v + ")");
94     }
95     maxMergeAtOnce = v;
96     return this;
97   }
98
99   /** @see #setMaxMergeAtOnce */
100   public int getMaxMergeAtOnce() {
101     return maxMergeAtOnce;
102   }
103
104   // TODO: should addIndexes do explicit merging, too?  And,
105   // if user calls IW.maybeMerge "explicitly"
106
107   /** Maximum number of segments to be merged at a time,
108    *  during optimize or expungeDeletes. Default is 30. */
109   public TieredMergePolicy setMaxMergeAtOnceExplicit(int v) {
110     if (v < 2) {
111       throw new IllegalArgumentException("maxMergeAtOnceExplicit must be > 1 (got " + v + ")");
112     }
113     maxMergeAtOnceExplicit = v;
114     return this;
115   }
116
117   /** @see #setMaxMergeAtOnceExplicit */
118   public int getMaxMergeAtOnceExplicit() {
119     return maxMergeAtOnceExplicit;
120   }
121
122   /** Maximum sized segment to produce during
123    *  normal merging.  This setting is approximate: the
124    *  estimate of the merged segment size is made by summing
125    *  sizes of to-be-merged segments (compensating for
126    *  percent deleted docs).  Default is 5 GB. */
127   public TieredMergePolicy setMaxMergedSegmentMB(double v) {
128     maxMergedSegmentBytes = (long) (v*1024*1024);
129     return this;
130   }
131
132   /** @see #getMaxMergedSegmentMB */
133   public double getMaxMergedSegmentMB() {
134     return maxMergedSegmentBytes/1024/1024.;
135   }
136
137   /** Controls how aggressively merges that reclaim more
138    *  deletions are favored.  Higher values favor selecting
139    *  merges that reclaim deletions.  A value of 0.0 means
140    *  deletions don't impact merge selection. */
141   public TieredMergePolicy setReclaimDeletesWeight(double v) {
142     if (v < 0.0) {
143       throw new IllegalArgumentException("reclaimDeletesWeight must be >= 0.0 (got " + v + ")");
144     }
145     reclaimDeletesWeight = v;
146     return this;
147   }
148
149   /** See {@link #setReclaimDeletesWeight}. */
150   public double getReclaimDeletesWeight() {
151     return reclaimDeletesWeight;
152   }
153
154   /** Segments smaller than this are "rounded up" to this
155    *  size, ie treated as equal (floor) size for merge
156    *  selection.  This is to prevent frequent flushing of
157    *  tiny segments from allowing a long tail in the index.
158    *  Default is 2 MB. */
159   public TieredMergePolicy setFloorSegmentMB(double v) {
160     if (v <= 0.0) {
161       throw new IllegalArgumentException("floorSegmentMB must be >= 0.0 (got " + v + ")");
162     }
163     floorSegmentBytes = (long) (v*1024*1024);
164     return this;
165   }
166
167   /** @see #setFloorSegmentMB */
168   public double getFloorSegmentMB() {
169     return floorSegmentBytes/1024*1024.;
170   }
171
172   /** When expungeDeletes is called, we only merge away a
173    *  segment if its delete percentage is over this
174    *  threshold.  Default is 10%. */ 
175   public TieredMergePolicy setExpungeDeletesPctAllowed(double v) {
176     if (v < 0.0 || v > 100.0) {
177       throw new IllegalArgumentException("expungeDeletesPctAllowed must be between 0.0 and 100.0 inclusive (got " + v + ")");
178     }
179     expungeDeletesPctAllowed = v;
180     return this;
181   }
182
183   /** @see #setExpungeDeletesPctAllowed */
184   public double getExpungeDeletesPctAllowed() {
185     return expungeDeletesPctAllowed;
186   }
187
188   /** Sets the allowed number of segments per tier.  Smaller
189    *  values mean more merging but fewer segments.
190    *
191    *  <p><b>NOTE</b>: this value should be >= the {@link
192    *  #setMaxMergeAtOnce} otherwise you'll force too much
193    *  merging to occur.</p>
194    *
195    *  <p>Default is 10.0.</p> */
196   public TieredMergePolicy setSegmentsPerTier(double v) {
197     if (v < 2.0) {
198       throw new IllegalArgumentException("segmentsPerTier must be >= 2.0 (got " + v + ")");
199     }
200     segsPerTier = v;
201     return this;
202   }
203
204   /** @see #setSegmentsPerTier */
205   public double getSegmentsPerTier() {
206     return segsPerTier;
207   }
208
209   /** Sets whether compound file format should be used for
210    *  newly flushed and newly merged segments.  Default
211    *  true. */
212   public TieredMergePolicy setUseCompoundFile(boolean useCompoundFile) {
213     this.useCompoundFile = useCompoundFile;
214     return this;
215   }
216
217   /** @see  #setUseCompoundFile */
218   public boolean getUseCompoundFile() {
219     return useCompoundFile;
220   }
221
222   /** If a merged segment will be more than this percentage
223    *  of the total size of the index, leave the segment as
224    *  non-compound file even if compound file is enabled.
225    *  Set to 1.0 to always use CFS regardless of merge
226    *  size.  Default is 0.1. */
227   public TieredMergePolicy setNoCFSRatio(double noCFSRatio) {
228     if (noCFSRatio < 0.0 || noCFSRatio > 1.0) {
229       throw new IllegalArgumentException("noCFSRatio must be 0.0 to 1.0 inclusive; got " + noCFSRatio);
230     }
231     this.noCFSRatio = noCFSRatio;
232     return this;
233   }
234   
235   /** @see #setNoCFSRatio */
236   public double getNoCFSRatio() {
237     return noCFSRatio;
238   }
239
240   private class SegmentByteSizeDescending implements Comparator<SegmentInfo> {
241     public int compare(SegmentInfo o1, SegmentInfo o2) {
242       try {
243         final long sz1 = size(o1);
244         final long sz2 = size(o2);
245         if (sz1 > sz2) {
246           return -1;
247         } else if (sz2 > sz1) {
248           return 1;
249         } else {
250           return o1.name.compareTo(o2.name);
251         }
252       } catch (IOException ioe) {
253         throw new RuntimeException(ioe);
254       }
255     }
256   }
257
258   private final Comparator<SegmentInfo> segmentByteSizeDescending = new SegmentByteSizeDescending();
259
260   protected static abstract class MergeScore {
261     abstract double getScore();
262     abstract String getExplanation();
263   }
264
265   @Override
266   public MergeSpecification findMerges(SegmentInfos infos) throws IOException {
267     if (verbose()) {
268       message("findMerges: " + infos.size() + " segments");
269     }
270     if (infos.size() == 0) {
271       return null;
272     }
273     final Collection<SegmentInfo> merging = writer.get().getMergingSegments();
274     final Collection<SegmentInfo> toBeMerged = new HashSet<SegmentInfo>();
275
276     final List<SegmentInfo> infosSorted = new ArrayList<SegmentInfo>(infos.asList());
277     Collections.sort(infosSorted, segmentByteSizeDescending);
278
279     // Compute total index bytes & print details about the index
280     long totIndexBytes = 0;
281     long minSegmentBytes = Long.MAX_VALUE;
282     for(SegmentInfo info : infosSorted) {
283       final long segBytes = size(info);
284       if (verbose()) {
285         String extra = merging.contains(info) ? " [merging]" : "";
286         if (segBytes >= maxMergedSegmentBytes/2.0) {
287           extra += " [skip: too large]";
288         } else if (segBytes < floorSegmentBytes) {
289           extra += " [floored]";
290         }
291         message("  seg=" + writer.get().segString(info) + " size=" + String.format("%.3f", segBytes/1024/1024.) + " MB" + extra);
292       }
293
294       minSegmentBytes = Math.min(segBytes, minSegmentBytes);
295       // Accum total byte size
296       totIndexBytes += segBytes;
297     }
298
299     // If we have too-large segments, grace them out
300     // of the maxSegmentCount:
301     int tooBigCount = 0;
302     while (tooBigCount < infosSorted.size() && size(infosSorted.get(tooBigCount)) >= maxMergedSegmentBytes/2.0) {
303       totIndexBytes -= size(infosSorted.get(tooBigCount));
304       tooBigCount++;
305     }
306
307     minSegmentBytes = floorSize(minSegmentBytes);
308
309     // Compute max allowed segs in the index
310     long levelSize = minSegmentBytes;
311     long bytesLeft = totIndexBytes;
312     double allowedSegCount = 0;
313     while(true) {
314       final double segCountLevel = bytesLeft / (double) levelSize;
315       if (segCountLevel < segsPerTier) {
316         allowedSegCount += Math.ceil(segCountLevel);
317         break;
318       }
319       allowedSegCount += segsPerTier;
320       bytesLeft -= segsPerTier * levelSize;
321       levelSize *= maxMergeAtOnce;
322     }
323     int allowedSegCountInt = (int) allowedSegCount;
324
325     MergeSpecification spec = null;
326
327     // Cycle to possibly select more than one merge:
328     while(true) {
329
330       long mergingBytes = 0;
331
332       // Gather eligible segments for merging, ie segments
333       // not already being merged and not already picked (by
334       // prior iteration of this loop) for merging:
335       final List<SegmentInfo> eligible = new ArrayList<SegmentInfo>();
336       for(int idx = tooBigCount; idx<infosSorted.size(); idx++) {
337         final SegmentInfo info = infosSorted.get(idx);
338         if (merging.contains(info)) {
339           mergingBytes += info.sizeInBytes(true);
340         } else if (!toBeMerged.contains(info)) {
341           eligible.add(info);
342         }
343       }
344
345       final boolean maxMergeIsRunning = mergingBytes >= maxMergedSegmentBytes;
346
347       message("  allowedSegmentCount=" + allowedSegCountInt + " vs count=" + infosSorted.size() + " (eligible count=" + eligible.size() + ") tooBigCount=" + tooBigCount);
348
349       if (eligible.size() == 0) {
350         return spec;
351       }
352
353       if (eligible.size() >= allowedSegCountInt) {
354
355         // OK we are over budget -- find best merge!
356         MergeScore bestScore = null;
357         List<SegmentInfo> best = null;
358         boolean bestTooLarge = false;
359         long bestMergeBytes = 0;
360
361         // Consider all merge starts:
362         for(int startIdx = 0;startIdx <= eligible.size()-maxMergeAtOnce; startIdx++) {
363
364           long totAfterMergeBytes = 0;
365
366           final List<SegmentInfo> candidate = new ArrayList<SegmentInfo>();
367           boolean hitTooLarge = false;
368           for(int idx = startIdx;idx<eligible.size() && candidate.size() < maxMergeAtOnce;idx++) {
369             final SegmentInfo info = eligible.get(idx);
370             final long segBytes = size(info);
371
372             if (totAfterMergeBytes + segBytes > maxMergedSegmentBytes) {
373               hitTooLarge = true;
374               // NOTE: we continue, so that we can try
375               // "packing" smaller segments into this merge
376               // to see if we can get closer to the max
377               // size; this in general is not perfect since
378               // this is really "bin packing" and we'd have
379               // to try different permutations.
380               continue;
381             }
382             candidate.add(info);
383             totAfterMergeBytes += segBytes;
384           }
385
386           final MergeScore score = score(candidate, hitTooLarge, mergingBytes);
387           message("  maybe=" + writer.get().segString(candidate) + " score=" + score.getScore() + " " + score.getExplanation() + " tooLarge=" + hitTooLarge + " size=" + String.format("%.3f MB", totAfterMergeBytes/1024./1024.));
388
389           // If we are already running a max sized merge
390           // (maxMergeIsRunning), don't allow another max
391           // sized merge to kick off:
392           if ((bestScore == null || score.getScore() < bestScore.getScore()) && (!hitTooLarge || !maxMergeIsRunning)) {
393             best = candidate;
394             bestScore = score;
395             bestTooLarge = hitTooLarge;
396             bestMergeBytes = totAfterMergeBytes;
397           }
398         }
399         
400         if (best != null) {
401           if (spec == null) {
402             spec = new MergeSpecification();
403           }
404           final OneMerge merge = new OneMerge(best);
405           spec.add(merge);
406           for(SegmentInfo info : merge.segments) {
407             toBeMerged.add(info);
408           }
409
410           if (verbose()) {
411             message("  add merge=" + writer.get().segString(merge.segments) + " size=" + String.format("%.3f MB", bestMergeBytes/1024./1024.) + " score=" + String.format("%.3f", bestScore.getScore()) + " " + bestScore.getExplanation() + (bestTooLarge ? " [max merge]" : ""));
412           }
413         } else {
414           return spec;
415         }
416       } else {
417         return spec;
418       }
419     }
420   }
421
422   /** Expert: scores one merge; subclasses can override. */
423   protected MergeScore score(List<SegmentInfo> candidate, boolean hitTooLarge, long mergingBytes) throws IOException {
424     long totBeforeMergeBytes = 0;
425     long totAfterMergeBytes = 0;
426     long totAfterMergeBytesFloored = 0;
427     for(SegmentInfo info : candidate) {
428       final long segBytes = size(info);
429       totAfterMergeBytes += segBytes;
430       totAfterMergeBytesFloored += floorSize(segBytes);
431       totBeforeMergeBytes += info.sizeInBytes(true);
432     }
433
434     // Measure "skew" of the merge, which can range
435     // from 1.0/numSegsBeingMerged (good) to 1.0
436     // (poor):
437     final double skew;
438     if (hitTooLarge) {
439       // Pretend the merge has perfect skew; skew doesn't
440       // matter in this case because this merge will not
441       // "cascade" and so it cannot lead to N^2 merge cost
442       // over time:
443       skew = 1.0/maxMergeAtOnce;
444     } else {
445       skew = ((double) floorSize(size(candidate.get(0))))/totAfterMergeBytesFloored;
446     }
447
448     // Strongly favor merges with less skew (smaller
449     // mergeScore is better):
450     double mergeScore = skew;
451
452     // Gently favor smaller merges over bigger ones.  We
453     // don't want to make this exponent too large else we
454     // can end up doing poor merges of small segments in
455     // order to avoid the large merges:
456     mergeScore *= Math.pow(totAfterMergeBytes, 0.05);
457
458     // Strongly favor merges that reclaim deletes:
459     final double nonDelRatio = ((double) totAfterMergeBytes)/totBeforeMergeBytes;
460     mergeScore *= Math.pow(nonDelRatio, reclaimDeletesWeight);
461
462     final double finalMergeScore = mergeScore;
463
464     return new MergeScore() {
465
466       @Override
467       public double getScore() {
468         return finalMergeScore;
469       }
470
471       @Override
472       public String getExplanation() {
473         return "skew=" + String.format("%.3f", skew) + " nonDelRatio=" + String.format("%.3f", nonDelRatio);
474       }
475     };
476   }
477
478   @Override
479   public MergeSpecification findMergesForOptimize(SegmentInfos infos, int maxSegmentCount, Map<SegmentInfo,Boolean> segmentsToOptimize) throws IOException {
480     if (verbose()) {
481       message("findMergesForOptimize maxSegmentCount=" + maxSegmentCount + " infos=" + writer.get().segString(infos) + " segmentsToOptimize=" + segmentsToOptimize);
482     }
483
484     List<SegmentInfo> eligible = new ArrayList<SegmentInfo>();
485     boolean optimizeMergeRunning = false;
486     final Collection<SegmentInfo> merging = writer.get().getMergingSegments();
487     boolean segmentIsOriginal = false;
488     for(SegmentInfo info : infos) {
489       final Boolean isOriginal = segmentsToOptimize.get(info);
490       if (isOriginal != null) {
491         segmentIsOriginal = isOriginal;
492         if (!merging.contains(info)) {
493           eligible.add(info);
494         } else {
495           optimizeMergeRunning = true;
496         }
497       }
498     }
499
500     if (eligible.size() == 0) {
501       return null;
502     }
503
504     if ((maxSegmentCount > 1 && eligible.size() <= maxSegmentCount) ||
505         (maxSegmentCount == 1 && eligible.size() == 1 && (!segmentIsOriginal || isOptimized(eligible.get(0))))) {
506       if (verbose()) {
507         message("already optimized");
508       }
509       return null;
510     }
511
512     Collections.sort(eligible, segmentByteSizeDescending);
513
514     if (verbose()) {
515       message("eligible=" + eligible);
516       message("optimizeMergeRunning=" + optimizeMergeRunning);
517     }
518
519     int end = eligible.size();
520     
521     MergeSpecification spec = null;
522
523     // Do full merges, first, backwards:
524     while(end >= maxMergeAtOnceExplicit + maxSegmentCount - 1) {
525       if (spec == null) {
526         spec = new MergeSpecification();
527       }
528       final OneMerge merge = new OneMerge(eligible.subList(end-maxMergeAtOnceExplicit, end));
529       if (verbose()) {
530         message("add merge=" + writer.get().segString(merge.segments));
531       }
532       spec.add(merge);
533       end -= maxMergeAtOnceExplicit;
534     }
535
536     if (spec == null && !optimizeMergeRunning) {
537       // Do final merge
538       final int numToMerge = end - maxSegmentCount + 1;
539       final OneMerge merge = new OneMerge(eligible.subList(end-numToMerge, end));
540       if (verbose()) {
541         message("add final merge=" + merge.segString(writer.get().getDirectory()));
542       }
543       spec = new MergeSpecification();
544       spec.add(merge);
545     }
546
547     return spec;
548   }
549
550   @Override
551   public MergeSpecification findMergesToExpungeDeletes(SegmentInfos infos)
552       throws CorruptIndexException, IOException {
553     if (verbose()) {
554       message("findMergesToExpungeDeletes infos=" + writer.get().segString(infos) + " expungeDeletesPctAllowed=" + expungeDeletesPctAllowed);
555     }
556     final List<SegmentInfo> eligible = new ArrayList<SegmentInfo>();
557     final Collection<SegmentInfo> merging = writer.get().getMergingSegments();
558     for(SegmentInfo info : infos) {
559       double pctDeletes = 100.*((double) writer.get().numDeletedDocs(info))/info.docCount;
560       if (pctDeletes > expungeDeletesPctAllowed && !merging.contains(info)) {
561         eligible.add(info);
562       }
563     }
564
565     if (eligible.size() == 0) {
566       return null;
567     }
568
569     Collections.sort(eligible, segmentByteSizeDescending);
570
571     if (verbose()) {
572       message("eligible=" + eligible);
573     }
574
575     int start = 0;
576     MergeSpecification spec = null;
577
578     while(start < eligible.size()) {
579       long totAfterMergeBytes = 0;
580       int upto = start;
581       boolean done = false;
582       while(upto < start + maxMergeAtOnceExplicit) {
583         if (upto == eligible.size()) {
584           done = true;
585           break;
586         }
587         final SegmentInfo info = eligible.get(upto);
588         final long segBytes = size(info);
589         if (totAfterMergeBytes + segBytes > maxMergedSegmentBytes) {
590           // TODO: we could be smarter here, eg cherry
591           // picking smaller merges that'd sum up to just
592           // around the max size
593           break;
594         }
595         totAfterMergeBytes += segBytes;
596         upto++;
597       }
598
599       if (upto == start) {
600         // Single segment is too big; grace it
601         start++;
602         continue;
603       }
604       
605       if (spec == null) {
606         spec = new MergeSpecification();
607       }
608
609       final OneMerge merge = new OneMerge(eligible.subList(start, upto));
610       if (verbose()) {
611         message("add merge=" + writer.get().segString(merge.segments));
612       }
613       spec.add(merge);
614       start = upto;
615       if (done) {
616         break;
617       }
618     }
619
620     return spec;
621   }
622
623   @Override
624   public boolean useCompoundFile(SegmentInfos infos, SegmentInfo mergedInfo) throws IOException {
625     final boolean doCFS;
626
627     if (!useCompoundFile) {
628       doCFS = false;
629     } else if (noCFSRatio == 1.0) {
630       doCFS = true;
631     } else {
632       long totalSize = 0;
633       for (SegmentInfo info : infos)
634         totalSize += size(info);
635
636       doCFS = size(mergedInfo) <= noCFSRatio * totalSize;
637     }
638     return doCFS;
639   }
640
641   @Override
642   public void close() {
643   }
644
645   private boolean isOptimized(SegmentInfo info)
646     throws IOException {
647     IndexWriter w = writer.get();
648     assert w != null;
649     boolean hasDeletions = w.numDeletedDocs(info) > 0;
650     return !hasDeletions &&
651       !info.hasSeparateNorms() &&
652       info.dir == w.getDirectory() &&
653       (info.getUseCompoundFile() == useCompoundFile || noCFSRatio < 1.0);
654   }
655
656   // Segment size in bytes, pro-rated by % deleted
657   private long size(SegmentInfo info) throws IOException {
658     final long byteSize = info.sizeInBytes(true);    
659     final int delCount = writer.get().numDeletedDocs(info);
660     final double delRatio = (info.docCount <= 0 ? 0.0f : ((double)delCount / (double)info.docCount));    
661     assert delRatio <= 1.0;
662     return (long) (byteSize * (1.0-delRatio));
663   }
664
665   private long floorSize(long bytes) {
666     return Math.max(floorSegmentBytes, bytes);
667   }
668
669   private boolean verbose() {
670     IndexWriter w = writer.get();
671     return w != null && w.verbose();
672   }
673
674   private void message(String message) {
675     if (verbose()) {
676       writer.get().message("TMP: " + message);
677     }
678   }
679
680   @Override
681   public String toString() {
682     StringBuilder sb = new StringBuilder("[" + getClass().getSimpleName() + ": ");
683     sb.append("maxMergeAtOnce=").append(maxMergeAtOnce).append(", ");
684     sb.append("maxMergeAtOnceExplicit=").append(maxMergeAtOnceExplicit).append(", ");
685     sb.append("maxMergedSegmentMB=").append(maxMergedSegmentBytes/1024/1024.).append(", ");
686     sb.append("floorSegmentMB=").append(floorSegmentBytes/1024/1024.).append(", ");
687     sb.append("expungeDeletesPctAllowed=").append(expungeDeletesPctAllowed).append(", ");
688     sb.append("segmentsPerTier=").append(segsPerTier).append(", ");
689     sb.append("useCompoundFile=").append(useCompoundFile).append(", ");
690     sb.append("noCFSRatio=").append(noCFSRatio);
691     return sb.toString();
692   }
693 }