From f583b6c82749262199ef8fd2b7616f2bb0adcd7e Mon Sep 17 00:00:00 2001
From: Vladimir Davydov <vdavydov.dev@gmail.com>
Date: Tue, 15 Jan 2019 15:49:41 +0300
Subject: [PATCH] vinyl: add last level size to statistics

In order to estimate space amplification of a vinyl database, we need to
know the size of data stored at the last LSM tree level. So this patch
adds such a counter both per index and globablly.

Per-index it is reported under disk.last_level, in rows, bytes, bytes
after compression, and pages, just like any other disk counter.

Globablly it is repoted in bytes only under disk.data_compacted. Note,
to be consistent with disk.data, it doesn't include the last level of
secondary indexes.
---
 src/box/vinyl.c          |   3 +
 src/box/vy_lsm.c         |  20 +++-
 src/box/vy_lsm.h         |  12 ++
 src/box/vy_stat.h        |   2 +
 test/vinyl/stat.result   | 248 +++++++++++++++++++++++++++++++--------
 test/vinyl/stat.test.lua |  42 +++++++
 6 files changed, 275 insertions(+), 52 deletions(-)

diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index 6daf1297d9..d6117f4444 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -319,6 +319,7 @@ vy_info_append_disk(struct vy_env *env, struct info_handler *h)
 	info_table_begin(h, "disk");
 	info_append_int(h, "data", env->lsm_env.disk_data_size);
 	info_append_int(h, "index", env->lsm_env.disk_index_size);
+	info_append_int(h, "data_compacted", env->lsm_env.compacted_data_size);
 	info_table_end(h); /* disk */
 }
 
@@ -404,6 +405,8 @@ vinyl_index_stat(struct index *index, struct info_handler *h)
 
 	info_table_begin(h, "disk");
 	vy_info_append_disk_stmt_counter(h, NULL, &stat->disk.count);
+	vy_info_append_disk_stmt_counter(h, "last_level",
+					 &stat->disk.last_level_count);
 	info_table_begin(h, "statement");
 	info_append_int(h, "inserts", stat->disk.stmt.inserts);
 	info_append_int(h, "replaces", stat->disk.stmt.replaces);
diff --git a/src/box/vy_lsm.c b/src/box/vy_lsm.c
index 5a23d978b9..5eb3fd7709 100644
--- a/src/box/vy_lsm.c
+++ b/src/box/vy_lsm.c
@@ -241,7 +241,9 @@ vy_lsm_delete(struct vy_lsm *lsm)
 	lsm->env->lsm_count--;
 	lsm->env->compaction_queue_size -=
 			lsm->stat.disk.compaction.queue.bytes;
-
+	if (lsm->index_id == 0)
+		lsm->env->compacted_data_size -=
+			lsm->stat.disk.last_level_count.bytes;
 	if (lsm->pk != NULL)
 		vy_lsm_unref(lsm->pk);
 
@@ -752,6 +754,14 @@ vy_lsm_acct_range(struct vy_lsm *lsm, struct vy_range *range)
 	vy_disk_stmt_counter_add(&lsm->stat.disk.compaction.queue,
 				 &range->compaction_queue);
 	lsm->env->compaction_queue_size += range->compaction_queue.bytes;
+	if (!rlist_empty(&range->slices)) {
+		struct vy_slice *slice = rlist_last_entry(&range->slices,
+						struct vy_slice, in_range);
+		vy_disk_stmt_counter_add(&lsm->stat.disk.last_level_count,
+					 &slice->count);
+		if (lsm->index_id == 0)
+			lsm->env->compacted_data_size += slice->count.bytes;
+	}
 }
 
 void
@@ -761,6 +771,14 @@ vy_lsm_unacct_range(struct vy_lsm *lsm, struct vy_range *range)
 	vy_disk_stmt_counter_sub(&lsm->stat.disk.compaction.queue,
 				 &range->compaction_queue);
 	lsm->env->compaction_queue_size -= range->compaction_queue.bytes;
+	if (!rlist_empty(&range->slices)) {
+		struct vy_slice *slice = rlist_last_entry(&range->slices,
+						struct vy_slice, in_range);
+		vy_disk_stmt_counter_sub(&lsm->stat.disk.last_level_count,
+					 &slice->count);
+		if (lsm->index_id == 0)
+			lsm->env->compacted_data_size -= slice->count.bytes;
+	}
 }
 
 void
diff --git a/src/box/vy_lsm.h b/src/box/vy_lsm.h
index 266013cf80..740336274b 100644
--- a/src/box/vy_lsm.h
+++ b/src/box/vy_lsm.h
@@ -107,6 +107,17 @@ struct vy_lsm_env {
 	 * is consistent with index.bsize().
 	 */
 	int64_t disk_index_size;
+	/**
+	 * Min size of disk space required to store data of all
+	 * spaces of the database. In other words, the size of
+	 * disk space the database would occupy if all spaces were
+	 * compacted and there were no indexes. Accounted in bytes,
+	 * without taking into account disk compression. Estimated
+	 * as the size of data stored in the last level of primary
+	 * LSM trees. Along with disk_data_size and disk_index_size,
+	 * it can be used for evaluating space amplification.
+	 */
+	int64_t compacted_data_size;
 	/**
 	 * Size of data of all spaces that need to be compacted,
 	 * in bytes, without taking into account disk compression.
@@ -457,6 +468,7 @@ vy_lsm_remove_range(struct vy_lsm *lsm, struct vy_range *range);
  *    a range of the LSM tree.
  *  - vy_lsm::stat::disk::compaction::queue after compaction priority
  *    of a range is updated.
+ *  - vy_lsm::stat::disk::last_level_count after a range is compacted.
  */
 void
 vy_lsm_acct_range(struct vy_lsm *lsm, struct vy_range *range);
diff --git a/src/box/vy_stat.h b/src/box/vy_stat.h
index 46ac11ca21..3afdbb7fcd 100644
--- a/src/box/vy_stat.h
+++ b/src/box/vy_stat.h
@@ -139,6 +139,8 @@ struct vy_lsm_stat {
 	struct {
 		/** Number of statements stored on disk. */
 		struct vy_disk_stmt_counter count;
+		/** Number of statements stored in the last LSM level. */
+		struct vy_disk_stmt_counter last_level_count;
 		/** Statement statistics. */
 		struct vy_stmt_stat stmt;
 		/** Run iterator statistics. */
diff --git a/test/vinyl/stat.result b/test/vinyl/stat.result
index 0920e3da90..419d3e6c2b 100644
--- a/test/vinyl/stat.result
+++ b/test/vinyl/stat.result
@@ -159,24 +159,12 @@ istat()
     rows: 0
     bytes: 0
   disk:
-    index_size: 0
-    compaction:
-      input:
-        bytes_compressed: 0
-        pages: 0
-        rows: 0
-        bytes: 0
-      queue:
-        bytes_compressed: 0
-        pages: 0
-        rows: 0
-        bytes: 0
-      output:
-        bytes_compressed: 0
-        pages: 0
-        rows: 0
-        bytes: 0
-      count: 0
+    last_level:
+      bytes_compressed: 0
+      pages: 0
+      rows: 0
+      bytes: 0
+    rows: 0
     statement:
       inserts: 0
       replaces: 0
@@ -193,6 +181,7 @@ istat()
         bytes: 0
       count: 0
     bloom_size: 0
+    index_size: 0
     iterator:
       read:
         bytes_compressed: 0
@@ -206,10 +195,26 @@ istat()
       get:
         rows: 0
         bytes: 0
-    bytes: 0
+    compaction:
+      input:
+        bytes_compressed: 0
+        pages: 0
+        rows: 0
+        bytes: 0
+      queue:
+        bytes_compressed: 0
+        pages: 0
+        rows: 0
+        bytes: 0
+      output:
+        bytes_compressed: 0
+        pages: 0
+        rows: 0
+        bytes: 0
+      count: 0
     pages: 0
     bytes_compressed: 0
-    rows: 0
+    bytes: 0
   txw:
     bytes: 0
     rows: 0
@@ -249,6 +254,7 @@ gstat()
     page_index: 0
     bloom_filter: 0
   disk:
+    data_compacted: 0
     data: 0
     index: 0
   scheduler:
@@ -292,6 +298,14 @@ stat_diff(istat(), st)
   run_avg: 1
   run_count: 1
   disk:
+    last_level:
+      bytes: 26049
+      pages: 7
+      bytes_compressed: <bytes_compressed>
+      rows: 25
+    rows: 25
+    statement:
+      replaces: 25
     dump:
       input:
         rows: 25
@@ -302,14 +316,11 @@ stat_diff(istat(), st)
         pages: 7
         bytes_compressed: <bytes_compressed>
         rows: 25
+    bytes: 26049
     index_size: 294
-    rows: 25
-    bytes_compressed: <bytes_compressed>
     pages: 7
+    bytes_compressed: <bytes_compressed>
     bloom_size: 70
-    statement:
-      replaces: 25
-    bytes: 26049
   bytes: 26049
   put:
     rows: 25
@@ -332,6 +343,14 @@ wait(istat, st, 'disk.compaction.count', 1)
 stat_diff(istat(), st)
 ---
 - disk:
+    last_level:
+      bytes: 26042
+      pages: 6
+      bytes_compressed: <bytes_compressed>
+      rows: 25
+    rows: 25
+    statement:
+      replaces: 25
     dump:
       input:
         rows: 50
@@ -342,7 +361,10 @@ stat_diff(istat(), st)
         pages: 13
         bytes_compressed: <bytes_compressed>
         rows: 50
+    bytes: 26042
     index_size: 252
+    pages: 6
+    bytes_compressed: <bytes_compressed>
     compaction:
       input:
         bytes: 78140
@@ -355,12 +377,6 @@ stat_diff(istat(), st)
         pages: 13
         bytes_compressed: <bytes_compressed>
         rows: 50
-    rows: 25
-    bytes_compressed: <bytes_compressed>
-    pages: 6
-    statement:
-      replaces: 25
-    bytes: 26042
   put:
     rows: 50
     bytes: 53050
@@ -1012,24 +1028,12 @@ istat()
     rows: 0
     bytes: 0
   disk:
-    index_size: 1050
-    compaction:
-      input:
-        bytes_compressed: <bytes_compressed>
-        pages: 0
-        rows: 0
-        bytes: 0
-      queue:
-        bytes_compressed: <bytes_compressed>
-        pages: 0
-        rows: 0
-        bytes: 0
-      output:
-        bytes_compressed: <bytes_compressed>
-        pages: 0
-        rows: 0
-        bytes: 0
-      count: 0
+    last_level:
+      bytes_compressed: <bytes_compressed>
+      pages: 25
+      rows: 100
+      bytes: 104300
+    rows: 100
     statement:
       inserts: 0
       replaces: 100
@@ -1046,6 +1050,7 @@ istat()
         bytes: 0
       count: 0
     bloom_size: 140
+    index_size: 1050
     iterator:
       read:
         bytes_compressed: <bytes_compressed>
@@ -1059,10 +1064,26 @@ istat()
       get:
         rows: 0
         bytes: 0
-    bytes: 104300
+    compaction:
+      input:
+        bytes_compressed: <bytes_compressed>
+        pages: 0
+        rows: 0
+        bytes: 0
+      queue:
+        bytes_compressed: <bytes_compressed>
+        pages: 0
+        rows: 0
+        bytes: 0
+      output:
+        bytes_compressed: <bytes_compressed>
+        pages: 0
+        rows: 0
+        bytes: 0
+      count: 0
     pages: 25
     bytes_compressed: <bytes_compressed>
-    rows: 100
+    bytes: 104300
   txw:
     bytes: 0
     rows: 0
@@ -1102,6 +1123,7 @@ gstat()
     page_index: 1050
     bloom_filter: 140
   disk:
+    data_compacted: 104300
     data: 104300
     index: 1190
   scheduler:
@@ -1557,6 +1579,9 @@ test_run:cmd('restart server test')
 fiber = require('fiber')
 ---
 ...
+digest = require('digest')
+---
+...
 s = box.space.test
 ---
 ...
@@ -1586,6 +1611,127 @@ i:stat().disk.statement
 s:drop()
 ---
 ...
+--
+-- Last level size.
+--
+s = box.schema.space.create('test', {engine = 'vinyl'})
+---
+...
+i1 = s:create_index('i1', {parts = {1, 'unsigned'}})
+---
+...
+i2 = s:create_index('i2', {parts = {2, 'unsigned'}})
+---
+...
+i1:stat().disk.last_level
+---
+- bytes_compressed: <bytes_compressed>
+  pages: 0
+  rows: 0
+  bytes: 0
+...
+i2:stat().disk.last_level
+---
+- bytes_compressed: <bytes_compressed>
+  pages: 0
+  rows: 0
+  bytes: 0
+...
+box.stat.vinyl().disk.data_compacted
+---
+- 0
+...
+for i = 1, 100 do s:replace{i, i, digest.urandom(100)} end
+---
+...
+box.snapshot()
+---
+- ok
+...
+i1:stat().disk.last_level
+---
+- bytes_compressed: <bytes_compressed>
+  pages: 2
+  rows: 100
+  bytes: 11815
+...
+i2:stat().disk.last_level
+---
+- bytes_compressed: <bytes_compressed>
+  pages: 1
+  rows: 100
+  bytes: 1608
+...
+box.stat.vinyl().disk.data_compacted
+---
+- 11815
+...
+for i = 1, 100, 10 do s:replace{i, i * 1000, digest.urandom(100)} end
+---
+...
+box.snapshot()
+---
+- ok
+...
+i1:stat().disk.last_level
+---
+- bytes_compressed: <bytes_compressed>
+  pages: 2
+  rows: 100
+  bytes: 11815
+...
+i2:stat().disk.last_level
+---
+- bytes_compressed: <bytes_compressed>
+  pages: 1
+  rows: 100
+  bytes: 1608
+...
+box.stat.vinyl().disk.data_compacted
+---
+- 11815
+...
+i1:compact()
+---
+...
+while i1:stat().disk.compaction.count == 0 do fiber.sleep(0.01) end
+---
+...
+i1:stat().disk.last_level
+---
+- bytes_compressed: <bytes_compressed>
+  pages: 2
+  rows: 100
+  bytes: 11841
+...
+box.stat.vinyl().disk.data_compacted
+---
+- 11841
+...
+i2:compact()
+---
+...
+while i2:stat().disk.compaction.count == 0 do fiber.sleep(0.01) end
+---
+...
+i2:stat().disk.last_level
+---
+- bytes_compressed: <bytes_compressed>
+  pages: 1
+  rows: 110
+  bytes: 1794
+...
+box.stat.vinyl().disk.data_compacted
+---
+- 11841
+...
+s:drop()
+---
+...
+box.stat.vinyl().disk.data_compacted
+---
+- 0
+...
 test_run:cmd('switch default')
 ---
 - true
diff --git a/test/vinyl/stat.test.lua b/test/vinyl/stat.test.lua
index 818ec730ab..4a95568206 100644
--- a/test/vinyl/stat.test.lua
+++ b/test/vinyl/stat.test.lua
@@ -473,6 +473,7 @@ i:stat().disk.statement
 test_run:cmd('restart server test')
 
 fiber = require('fiber')
+digest = require('digest')
 
 s = box.space.test
 i = s.index.primary
@@ -486,6 +487,47 @@ i:stat().disk.statement
 
 s:drop()
 
+--
+-- Last level size.
+--
+s = box.schema.space.create('test', {engine = 'vinyl'})
+i1 = s:create_index('i1', {parts = {1, 'unsigned'}})
+i2 = s:create_index('i2', {parts = {2, 'unsigned'}})
+
+i1:stat().disk.last_level
+i2:stat().disk.last_level
+box.stat.vinyl().disk.data_compacted
+
+for i = 1, 100 do s:replace{i, i, digest.urandom(100)} end
+box.snapshot()
+
+i1:stat().disk.last_level
+i2:stat().disk.last_level
+box.stat.vinyl().disk.data_compacted
+
+for i = 1, 100, 10 do s:replace{i, i * 1000, digest.urandom(100)} end
+box.snapshot()
+
+i1:stat().disk.last_level
+i2:stat().disk.last_level
+box.stat.vinyl().disk.data_compacted
+
+i1:compact()
+while i1:stat().disk.compaction.count == 0 do fiber.sleep(0.01) end
+
+i1:stat().disk.last_level
+box.stat.vinyl().disk.data_compacted
+
+i2:compact()
+while i2:stat().disk.compaction.count == 0 do fiber.sleep(0.01) end
+
+i2:stat().disk.last_level
+box.stat.vinyl().disk.data_compacted
+
+s:drop()
+
+box.stat.vinyl().disk.data_compacted
+
 test_run:cmd('switch default')
 test_run:cmd('stop server test')
 test_run:cmd('cleanup server test')
-- 
GitLab