You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FileReftableStack.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766
  1. /*
  2. * Copyright (C) 2019 Google LLC
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.internal.storage.file;
  44. import static java.nio.charset.StandardCharsets.UTF_8;
  45. import java.io.BufferedReader;
  46. import java.io.File;
  47. import java.io.FileInputStream;
  48. import java.io.FileNotFoundException;
  49. import java.io.FileOutputStream;
  50. import java.io.IOException;
  51. import java.io.InputStreamReader;
  52. import java.nio.file.Files;
  53. import java.nio.file.StandardCopyOption;
  54. import java.util.ArrayList;
  55. import java.util.Comparator;
  56. import java.util.List;
  57. import java.util.Map;
  58. import java.util.Optional;
  59. import java.util.function.Supplier;
  60. import java.util.stream.Collectors;
  61. import org.eclipse.jgit.annotations.Nullable;
  62. import org.eclipse.jgit.errors.LockFailedException;
  63. import org.eclipse.jgit.internal.storage.io.BlockSource;
  64. import org.eclipse.jgit.internal.storage.reftable.MergedReftable;
  65. import org.eclipse.jgit.internal.storage.reftable.ReftableCompactor;
  66. import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
  67. import org.eclipse.jgit.internal.storage.reftable.ReftableReader;
  68. import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
  69. import org.eclipse.jgit.lib.Config;
  70. import org.eclipse.jgit.util.FileUtils;
  71. /**
  72. * A mutable stack of reftables on local filesystem storage. Not thread-safe.
  73. * This is an AutoCloseable because this object owns the file handles to the
  74. * open reftables.
  75. */
  76. public class FileReftableStack implements AutoCloseable {
  77. private static class StackEntry {
  78. String name;
  79. ReftableReader reftableReader;
  80. }
  81. private MergedReftable mergedReftable;
  82. private List<StackEntry> stack;
  83. private long lastNextUpdateIndex;
  84. private final File stackPath;
  85. private final File reftableDir;
  86. private final Runnable onChange;
  87. private final Supplier<Config> configSupplier;
  88. // Used for stats & testing.
  89. static class CompactionStats {
  90. long tables;
  91. long bytes;
  92. int attempted;
  93. int failed;
  94. long refCount;
  95. long logCount;
  96. CompactionStats() {
  97. tables = 0;
  98. bytes = 0;
  99. attempted = 0;
  100. failed = 0;
  101. logCount = 0;
  102. refCount = 0;
  103. }
  104. }
  105. private final CompactionStats stats;
  106. /**
  107. * Creates a stack corresponding to the list of reftables in the argument
  108. *
  109. * @param stackPath
  110. * the filename for the stack.
  111. * @param reftableDir
  112. * the dir holding the tables.
  113. * @param onChange
  114. * hook to call if we notice a new write
  115. * @param configSupplier
  116. * Config supplier
  117. * @throws IOException
  118. * on I/O problems
  119. */
  120. public FileReftableStack(File stackPath, File reftableDir,
  121. @Nullable Runnable onChange, Supplier<Config> configSupplier)
  122. throws IOException {
  123. this.stackPath = stackPath;
  124. this.reftableDir = reftableDir;
  125. this.stack = new ArrayList<>();
  126. this.configSupplier = configSupplier;
  127. this.onChange = onChange;
  128. // skip event notification
  129. lastNextUpdateIndex = 0;
  130. reload();
  131. stats = new CompactionStats();
  132. }
  133. CompactionStats getStats() {
  134. return stats;
  135. }
  136. /** Thrown if the update indices in the stack are not monotonic */
  137. public static class ReftableNumbersNotIncreasingException
  138. extends RuntimeException {
  139. private static final long serialVersionUID = 1L;
  140. String name;
  141. long lastMax;
  142. long min;
  143. ReftableNumbersNotIncreasingException(String name, long lastMax,
  144. long min) {
  145. this.name = name;
  146. this.lastMax = lastMax;
  147. this.min = min;
  148. }
  149. }
  150. /**
  151. * Reloads the stack, potentially reusing opened reftableReaders.
  152. *
  153. * @param names
  154. * holds the names of the tables to load.
  155. * @throws FileNotFoundException
  156. * load must be retried.
  157. * @throws IOException
  158. * on other IO errors.
  159. */
  160. private void reloadOnce(List<String> names)
  161. throws IOException, FileNotFoundException {
  162. Map<String, ReftableReader> current = stack.stream()
  163. .collect(Collectors.toMap(e -> e.name, e -> e.reftableReader));
  164. List<ReftableReader> newTables = new ArrayList<>();
  165. List<StackEntry> newStack = new ArrayList<>(stack.size() + 1);
  166. try {
  167. ReftableReader last = null;
  168. for (String name : names) {
  169. StackEntry entry = new StackEntry();
  170. entry.name = name;
  171. ReftableReader t = null;
  172. if (current.containsKey(name)) {
  173. t = current.remove(name);
  174. } else {
  175. File subtable = new File(reftableDir, name);
  176. FileInputStream is;
  177. is = new FileInputStream(subtable);
  178. t = new ReftableReader(BlockSource.from(is));
  179. newTables.add(t);
  180. }
  181. if (last != null) {
  182. // TODO: move this to MergedReftable
  183. if (last.maxUpdateIndex() >= t.minUpdateIndex()) {
  184. throw new ReftableNumbersNotIncreasingException(name,
  185. last.maxUpdateIndex(), t.minUpdateIndex());
  186. }
  187. }
  188. last = t;
  189. entry.reftableReader = t;
  190. newStack.add(entry);
  191. }
  192. // survived without exceptions: swap in new stack, and close
  193. // dangling tables.
  194. stack = newStack;
  195. newTables.clear();
  196. current.values().forEach(r -> {
  197. try {
  198. r.close();
  199. } catch (IOException e) {
  200. throw new AssertionError(e);
  201. }
  202. });
  203. } finally {
  204. newTables.forEach(t -> {
  205. try {
  206. t.close();
  207. } catch (IOException ioe) {
  208. // reader close should not generate errors.
  209. throw new AssertionError(ioe);
  210. }
  211. });
  212. }
  213. }
  214. void reload() throws IOException {
  215. // Try for 2.5 seconds.
  216. long deadline = System.currentTimeMillis() + 2500;
  217. // A successful reftable transaction is 2 atomic file writes
  218. // (open, write, close, rename), which a fast Linux system should be
  219. // able to do in about ~200us. So 1 ms should be ample time.
  220. long min = 1;
  221. long max = 1000;
  222. long delay = 0;
  223. boolean success = false;
  224. // Don't check deadline for the first 3 retries, so we can step with a
  225. // debugger without worrying about deadlines.
  226. int tries = 0;
  227. while (tries < 3 || System.currentTimeMillis() < deadline) {
  228. List<String> names = readTableNames();
  229. tries++;
  230. try {
  231. reloadOnce(names);
  232. success = true;
  233. break;
  234. } catch (FileNotFoundException e) {
  235. List<String> changed = readTableNames();
  236. if (changed.equals(names)) {
  237. throw e;
  238. }
  239. }
  240. delay = FileUtils.delay(delay, min, max);
  241. try {
  242. Thread.sleep(delay);
  243. } catch (InterruptedException e) {
  244. Thread.currentThread().interrupt();
  245. throw new RuntimeException(e);
  246. }
  247. }
  248. if (!success) {
  249. throw new LockFailedException(stackPath);
  250. }
  251. mergedReftable = new MergedReftable(stack.stream()
  252. .map(x -> x.reftableReader).collect(Collectors.toList()));
  253. long curr = nextUpdateIndex();
  254. if (lastNextUpdateIndex > 0 && lastNextUpdateIndex != curr
  255. && onChange != null) {
  256. onChange.run();
  257. }
  258. lastNextUpdateIndex = curr;
  259. }
  260. /**
  261. * @return the merged reftable
  262. */
  263. public MergedReftable getMergedReftable() {
  264. return mergedReftable;
  265. }
  266. /**
  267. * Writer is a callable that writes data to a reftable under construction.
  268. * It should set the min/max update index, and then write refs and/or logs.
  269. * It should not call finish() on the writer.
  270. */
  271. public interface Writer {
  272. /**
  273. * Write data to reftable
  274. *
  275. * @param w
  276. * writer to use
  277. * @throws IOException
  278. */
  279. void call(ReftableWriter w) throws IOException;
  280. }
  281. private List<String> readTableNames() throws IOException {
  282. List<String> names = new ArrayList<>(stack.size() + 1);
  283. try (BufferedReader br = new BufferedReader(
  284. new InputStreamReader(new FileInputStream(stackPath), UTF_8))) {
  285. String line;
  286. while ((line = br.readLine()) != null) {
  287. if (!line.isEmpty()) {
  288. names.add(line);
  289. }
  290. }
  291. } catch (FileNotFoundException e) {
  292. // file isn't there: empty repository.
  293. }
  294. return names;
  295. }
  296. /**
  297. * @return true if the on-disk file corresponds to the in-memory data.
  298. * @throws IOException
  299. * on IO problem
  300. */
  301. boolean isUpToDate() throws IOException {
  302. // We could use FileSnapshot to avoid reading the file, but the file is
  303. // small so it's probably a minor optimization.
  304. try {
  305. List<String> names = readTableNames();
  306. if (names.size() != stack.size()) {
  307. return false;
  308. }
  309. for (int i = 0; i < names.size(); i++) {
  310. if (!names.get(i).equals(stack.get(i).name)) {
  311. return false;
  312. }
  313. }
  314. } catch (FileNotFoundException e) {
  315. return stack.isEmpty();
  316. }
  317. return true;
  318. }
  319. /**
  320. * {@inheritDoc}
  321. */
  322. @Override
  323. public void close() {
  324. for (StackEntry entry : stack) {
  325. try {
  326. entry.reftableReader.close();
  327. } catch (Exception e) {
  328. // we are reading; this should never fail.
  329. throw new AssertionError(e);
  330. }
  331. }
  332. }
  333. private long nextUpdateIndex() throws IOException {
  334. return stack.size() > 0
  335. ? stack.get(stack.size() - 1).reftableReader.maxUpdateIndex()
  336. + 1
  337. : 1;
  338. }
  339. private String filename(long low, long high) {
  340. return String.format("%012x-%012x", //$NON-NLS-1$
  341. Long.valueOf(low), Long.valueOf(high));
  342. }
  343. /**
  344. * Tries to add a new reftable to the stack. Returns true if it succeeded,
  345. * or false if there was a lock failure, due to races with other processes.
  346. * This is package private so FileReftableDatabase can call into here.
  347. *
  348. * @param w
  349. * writer to write data to a reftable under construction
  350. * @return true if the transaction was successful.
  351. * @throws IOException
  352. * on I/O problems
  353. */
  354. @SuppressWarnings("nls")
  355. public boolean addReftable(Writer w) throws IOException {
  356. LockFile lock = new LockFile(stackPath);
  357. try {
  358. if (!lock.lockForAppend()) {
  359. return false;
  360. }
  361. if (!isUpToDate()) {
  362. return false;
  363. }
  364. String fn = filename(nextUpdateIndex(), nextUpdateIndex());
  365. File tmpTable = File.createTempFile(fn + "_", ".ref",
  366. stackPath.getParentFile());
  367. ReftableWriter.Stats s;
  368. try (FileOutputStream fos = new FileOutputStream(tmpTable)) {
  369. ReftableWriter rw = new ReftableWriter(reftableConfig(), fos);
  370. w.call(rw);
  371. rw.finish();
  372. s = rw.getStats();
  373. }
  374. if (s.minUpdateIndex() < nextUpdateIndex()) {
  375. return false;
  376. }
  377. // The spec says to name log-only files with .log, which is somewhat
  378. // pointless given compaction, but we do so anyway.
  379. fn += s.refCount() > 0 ? ".ref" : ".log";
  380. File dest = new File(reftableDir, fn);
  381. FileUtils.rename(tmpTable, dest, StandardCopyOption.ATOMIC_MOVE);
  382. lock.write((fn + "\n").getBytes(UTF_8));
  383. if (!lock.commit()) {
  384. FileUtils.delete(dest);
  385. return false;
  386. }
  387. reload();
  388. autoCompact();
  389. } finally {
  390. lock.unlock();
  391. }
  392. return true;
  393. }
  394. private ReftableConfig reftableConfig() {
  395. return new ReftableConfig(configSupplier.get());
  396. }
  397. /**
  398. * Write the reftable for the given range into a temp file.
  399. *
  400. * @param first
  401. * index of first stack entry to be written
  402. * @param last
  403. * index of last stack entry to be written
  404. * @return the file holding the replacement table.
  405. * @throws IOException
  406. * on I/O problem
  407. */
  408. private File compactLocked(int first, int last) throws IOException {
  409. String fn = filename(first, last);
  410. File tmpTable = File.createTempFile(fn + "_", ".ref", //$NON-NLS-1$//$NON-NLS-2$
  411. stackPath.getParentFile());
  412. try (FileOutputStream fos = new FileOutputStream(tmpTable)) {
  413. ReftableCompactor c = new ReftableCompactor(fos)
  414. .setConfig(reftableConfig())
  415. .setIncludeDeletes(first > 0);
  416. List<ReftableReader> compactMe = new ArrayList<>();
  417. long totalBytes = 0;
  418. for (int i = first; i <= last; i++) {
  419. compactMe.add(stack.get(i).reftableReader);
  420. totalBytes += stack.get(i).reftableReader.size();
  421. }
  422. c.addAll(compactMe);
  423. c.compact();
  424. // Even though the compaction did not definitely succeed, we keep
  425. // tally here as we've expended the effort.
  426. stats.bytes += totalBytes;
  427. stats.tables += first - last + 1;
  428. stats.attempted++;
  429. stats.refCount += c.getStats().refCount();
  430. stats.logCount += c.getStats().logCount();
  431. }
  432. return tmpTable;
  433. }
  434. /**
  435. * Compacts a range of the stack, following the file locking protocol
  436. * documented in the spec.
  437. *
  438. * @param first
  439. * index of first stack entry to be considered in compaction
  440. * @param last
  441. * index of last stack entry to be considered in compaction
  442. * @return true if a compaction was successfully applied.
  443. * @throws IOException
  444. * on I/O problem
  445. */
  446. boolean compactRange(int first, int last) throws IOException {
  447. if (first >= last) {
  448. return true;
  449. }
  450. LockFile lock = new LockFile(stackPath);
  451. File tmpTable = null;
  452. List<LockFile> subtableLocks = new ArrayList<>();
  453. try {
  454. if (!lock.lock()) {
  455. return false;
  456. }
  457. if (!isUpToDate()) {
  458. return false;
  459. }
  460. List<File> deleteOnSuccess = new ArrayList<>();
  461. for (int i = first; i <= last; i++) {
  462. File f = new File(reftableDir, stack.get(i).name);
  463. LockFile lf = new LockFile(f);
  464. if (!lf.lock()) {
  465. return false;
  466. }
  467. subtableLocks.add(lf);
  468. deleteOnSuccess.add(f);
  469. }
  470. lock.unlock();
  471. lock = null;
  472. tmpTable = compactLocked(first, last);
  473. lock = new LockFile(stackPath);
  474. if (!lock.lock()) {
  475. return false;
  476. }
  477. if (!isUpToDate()) {
  478. return false;
  479. }
  480. String fn = filename(
  481. stack.get(first).reftableReader.minUpdateIndex(),
  482. stack.get(last).reftableReader.maxUpdateIndex());
  483. // The spec suggests to use .log for log-only tables, and collect
  484. // all log entries in a single file at the bottom of the stack. That would
  485. // require supporting overlapping ranges for the different tables. For the
  486. // sake of simplicity, we simply ignore this and always produce a log +
  487. // ref combined table.
  488. fn += ".ref"; //$NON-NLS-1$
  489. File dest = new File(reftableDir, fn);
  490. FileUtils.rename(tmpTable, dest, StandardCopyOption.ATOMIC_MOVE);
  491. tmpTable = null;
  492. StringBuilder sb = new StringBuilder();
  493. for (int i = 0; i < first; i++) {
  494. sb.append(stack.get(i).name + "\n"); //$NON-NLS-1$
  495. }
  496. sb.append(fn + "\n"); //$NON-NLS-1$
  497. for (int i = last + 1; i < stack.size(); i++) {
  498. sb.append(stack.get(i).name + "\n"); //$NON-NLS-1$
  499. }
  500. lock.write(sb.toString().getBytes(UTF_8));
  501. if (!lock.commit()) {
  502. dest.delete();
  503. return false;
  504. }
  505. for (File f : deleteOnSuccess) {
  506. Files.delete(f.toPath());
  507. }
  508. reload();
  509. return true;
  510. } finally {
  511. if (tmpTable != null) {
  512. tmpTable.delete();
  513. }
  514. for (LockFile lf : subtableLocks) {
  515. lf.unlock();
  516. }
  517. if (lock != null) {
  518. lock.unlock();
  519. }
  520. }
  521. }
  522. /**
  523. * Calculate an approximate log2.
  524. *
  525. * @param sz
  526. * @return log2
  527. */
  528. static int log(long sz) {
  529. long base = 2;
  530. if (sz <= 0) {
  531. throw new IllegalArgumentException("log2 negative"); //$NON-NLS-1$
  532. }
  533. int l = 0;
  534. while (sz > 0) {
  535. l++;
  536. sz /= base;
  537. }
  538. return l - 1;
  539. }
  540. /**
  541. * A segment is a consecutive list of reftables of the same approximate
  542. * size.
  543. */
  544. static class Segment {
  545. // the approximate log_2 of the size.
  546. int log;
  547. // The total bytes in this segment
  548. long bytes;
  549. int start;
  550. int end; // exclusive.
  551. int size() {
  552. return end - start;
  553. }
  554. Segment(int start, int end, int log, long bytes) {
  555. this.log = log;
  556. this.start = start;
  557. this.end = end;
  558. this.bytes = bytes;
  559. }
  560. Segment() {
  561. this(0, 0, 0, 0);
  562. }
  563. @Override
  564. public int hashCode() {
  565. return 0; // appease error-prone
  566. }
  567. @Override
  568. public boolean equals(Object other) {
  569. Segment o = (Segment) other;
  570. return o.bytes == bytes && o.log == log && o.start == start
  571. && o.end == end;
  572. }
  573. @SuppressWarnings("boxing")
  574. @Override
  575. public String toString() {
  576. return String.format("{ [%d,%d) l=%d sz=%d }", start, end, log, //$NON-NLS-1$
  577. bytes);
  578. }
  579. }
  580. static List<Segment> segmentSizes(long[] sizes) {
  581. List<Segment> segments = new ArrayList<>();
  582. Segment cur = new Segment();
  583. for (int i = 0; i < sizes.length; i++) {
  584. int l = log(sizes[i]);
  585. if (l != cur.log && cur.bytes > 0) {
  586. segments.add(cur);
  587. cur = new Segment();
  588. cur.start = i;
  589. cur.log = l;
  590. }
  591. cur.log = l;
  592. cur.end = i + 1;
  593. cur.bytes += sizes[i];
  594. }
  595. segments.add(cur);
  596. return segments;
  597. }
  598. private static Optional<Segment> autoCompactCandidate(long[] sizes) {
  599. if (sizes.length == 0) {
  600. return Optional.empty();
  601. }
  602. // The cost of compaction is proportional to the size, and we want to
  603. // avoid frequent large compactions. We do this by playing the game 2048
  604. // here: first compact together the smallest tables if there are more
  605. // than one. Then try to see if the result will be big enough to match
  606. // up with next up.
  607. List<Segment> segments = segmentSizes(sizes);
  608. segments = segments.stream().filter(s -> s.size() > 1)
  609. .collect(Collectors.toList());
  610. if (segments.isEmpty()) {
  611. return Optional.empty();
  612. }
  613. Optional<Segment> optMinSeg = segments.stream()
  614. .min(Comparator.comparing(s -> Integer.valueOf(s.log)));
  615. // Input is non-empty, so always present.
  616. Segment smallCollected = optMinSeg.get();
  617. while (smallCollected.start > 0) {
  618. int prev = smallCollected.start - 1;
  619. long prevSize = sizes[prev];
  620. if (log(smallCollected.bytes) < log(prevSize)) {
  621. break;
  622. }
  623. smallCollected.start = prev;
  624. smallCollected.bytes += prevSize;
  625. }
  626. return Optional.of(smallCollected);
  627. }
  628. /**
  629. * Heuristically tries to compact the stack if the stack has a suitable
  630. * shape.
  631. *
  632. * @throws IOException
  633. */
  634. private void autoCompact() throws IOException {
  635. Optional<Segment> cand = autoCompactCandidate(tableSizes());
  636. if (cand.isPresent()) {
  637. if (!compactRange(cand.get().start, cand.get().end - 1)) {
  638. stats.failed++;
  639. }
  640. }
  641. }
  642. // 68b footer, 24b header = 92.
  643. private static long OVERHEAD = 91;
  644. private long[] tableSizes() throws IOException {
  645. long[] sizes = new long[stack.size()];
  646. for (int i = 0; i < stack.size(); i++) {
  647. // If we don't subtract the overhead, the file size isn't
  648. // proportional to the number of entries. This will cause us to
  649. // compact too often, which is expensive.
  650. sizes[i] = stack.get(i).reftableReader.size() - OVERHEAD;
  651. }
  652. return sizes;
  653. }
  654. void compactFully() throws IOException {
  655. if (!compactRange(0, stack.size() - 1)) {
  656. stats.failed++;
  657. }
  658. }
  659. }