pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.5.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  *  forceMerge (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 forceMergeDeletesPctAllowed = 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    *  forceMerge or forceMergeDeletes 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 forceMerge or forceMergeDeletes. 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 forceMergeDeletes is called, we only merge away a
173    *  segment if its delete percentage is over this
174    *  threshold.  Default is 10%. */ 
175   public TieredMergePolicy setForceMergeDeletesPctAllowed(double v) {
176     if (v < 0.0 || v > 100.0) {
177       throw new IllegalArgumentException("forceMergeDeletesPctAllowed must be between 0.0 and 100.0 inclusive (got " + v + ")");
178     }
179     forceMergeDeletesPctAllowed = v;
180     return this;
181   }
182
183   /** @see #setForceMergeDeletesPctAllowed */
184   public double getForceMergeDeletesPctAllowed() {
185     return forceMergeDeletesPctAllowed;
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 findForcedMerges(SegmentInfos infos, int maxSegmentCount, Map<SegmentInfo,Boolean> segmentsToMerge) throws IOException {
480     if (verbose()) {
481       message("findForcedMerges maxSegmentCount=" + maxSegmentCount + " infos=" + writer.get().segString(infos) + " segmentsToMerge=" + segmentsToMerge);
482     }
483
484     List<SegmentInfo> eligible = new ArrayList<SegmentInfo>();
485     boolean forceMergeRunning = false;
486     final Collection<SegmentInfo> merging = writer.get().getMergingSegments();
487     boolean segmentIsOriginal = false;
488     for(SegmentInfo info : infos) {
489       final Boolean isOriginal = segmentsToMerge.get(info);
490       if (isOriginal != null) {
491         segmentIsOriginal = isOriginal;
492         if (!merging.contains(info)) {
493           eligible.add(info);
494         } else {
495           forceMergeRunning = 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 || isMerged(eligible.get(0))))) {
506       if (verbose()) {
507         message("already merged");
508       }
509       return null;
510     }
511
512     Collections.sort(eligible, segmentByteSizeDescending);
513
514     if (verbose()) {
515       message("eligible=" + eligible);
516       message("forceMergeRunning=" + forceMergeRunning);
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 && !forceMergeRunning) {
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 findForcedDeletesMerges(SegmentInfos infos)
552       throws CorruptIndexException, IOException {
553     if (verbose()) {
554       message("findForcedDeletesMerges infos=" + writer.get().segString(infos) + " forceMergeDeletesPctAllowed=" + forceMergeDeletesPctAllowed);
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 > forceMergeDeletesPctAllowed && !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       // Don't enforce max merged size here: app is explicitly
580       // calling forceMergeDeletes, and knows this may take a
581       // long time / produce big segments (like forceMerge):
582       final int end = Math.min(start + maxMergeAtOnceExplicit, eligible.size());
583       if (spec == null) {
584         spec = new MergeSpecification();
585       }
586
587       final OneMerge merge = new OneMerge(eligible.subList(start, end));
588       if (verbose()) {
589         message("add merge=" + writer.get().segString(merge.segments));
590       }
591       spec.add(merge);
592       start = end;
593     }
594
595     return spec;
596   }
597
598   @Override
599   public boolean useCompoundFile(SegmentInfos infos, SegmentInfo mergedInfo) throws IOException {
600     final boolean doCFS;
601
602     if (!useCompoundFile) {
603       doCFS = false;
604     } else if (noCFSRatio == 1.0) {
605       doCFS = true;
606     } else {
607       long totalSize = 0;
608       for (SegmentInfo info : infos)
609         totalSize += size(info);
610
611       doCFS = size(mergedInfo) <= noCFSRatio * totalSize;
612     }
613     return doCFS;
614   }
615
616   @Override
617   public void close() {
618   }
619
620   private boolean isMerged(SegmentInfo info)
621     throws IOException {
622     IndexWriter w = writer.get();
623     assert w != null;
624     boolean hasDeletions = w.numDeletedDocs(info) > 0;
625     return !hasDeletions &&
626       !info.hasSeparateNorms() &&
627       info.dir == w.getDirectory() &&
628       (info.getUseCompoundFile() == useCompoundFile || noCFSRatio < 1.0);
629   }
630
631   // Segment size in bytes, pro-rated by % deleted
632   private long size(SegmentInfo info) throws IOException {
633     final long byteSize = info.sizeInBytes(true);    
634     final int delCount = writer.get().numDeletedDocs(info);
635     final double delRatio = (info.docCount <= 0 ? 0.0f : ((double)delCount / (double)info.docCount));    
636     assert delRatio <= 1.0;
637     return (long) (byteSize * (1.0-delRatio));
638   }
639
640   private long floorSize(long bytes) {
641     return Math.max(floorSegmentBytes, bytes);
642   }
643
644   private boolean verbose() {
645     IndexWriter w = writer.get();
646     return w != null && w.verbose();
647   }
648
649   private void message(String message) {
650     if (verbose()) {
651       writer.get().message("TMP: " + message);
652     }
653   }
654
655   @Override
656   public String toString() {
657     StringBuilder sb = new StringBuilder("[" + getClass().getSimpleName() + ": ");
658     sb.append("maxMergeAtOnce=").append(maxMergeAtOnce).append(", ");
659     sb.append("maxMergeAtOnceExplicit=").append(maxMergeAtOnceExplicit).append(", ");
660     sb.append("maxMergedSegmentMB=").append(maxMergedSegmentBytes/1024/1024.).append(", ");
661     sb.append("floorSegmentMB=").append(floorSegmentBytes/1024/1024.).append(", ");
662     sb.append("forceMergeDeletesPctAllowed=").append(forceMergeDeletesPctAllowed).append(", ");
663     sb.append("segmentsPerTier=").append(segsPerTier).append(", ");
664     sb.append("useCompoundFile=").append(useCompoundFile).append(", ");
665     sb.append("noCFSRatio=").append(noCFSRatio);
666     return sb.toString();
667   }
668 }