From 5a78b4c746555d4c1891ce82d4c0b2d23125caff Mon Sep 17 00:00:00 2001
From: "ingo@mysql.com" <>
Date: Mon, 3 May 2004 15:55:21 +0200
Subject: [PATCH] WL#1700 - Properly count key_blocks_used and
 key_blocks_current. Introduced a new free blocks list. Free blocks are now
 re-used before new blocks are allocated from the pool. There is a new status
 variable which can be queried by "show status like key_blocks_unused".

---
 include/keycache.h            |  5 ++-
 mysql-test/r/key_cache.result | 24 ++++++++++
 mysql-test/t/key_cache.test   | 12 +++++
 mysys/mf_keycache.c           | 84 ++++++++++++++++++++++++++++-------
 sql/mysqld.cc                 |  6 ++-
 sql/sql_show.cc               |  1 +
 sql/sql_test.cc               |  2 +-
 sql/structs.h                 |  3 +-
 8 files changed, 115 insertions(+), 22 deletions(-)

diff --git a/include/keycache.h b/include/keycache.h
index b882f3127f5..26ee0ccadb1 100644
--- a/include/keycache.h
+++ b/include/keycache.h
@@ -57,7 +57,8 @@ typedef struct st_key_cache
   int hash_links;                /* max number of hash links                 */
   int hash_links_used;           /* number of hash links currently used      */
   int disk_blocks;               /* max number of blocks in the cache        */
-  ulong blocks_used;             /* number of currently used blocks          */
+  ulong blocks_used; /* maximum number of concurrently used blocks */
+  ulong blocks_unused; /* number of currently unused blocks */
   ulong blocks_changed;          /* number of currently dirty blocks         */
   ulong warm_blocks;             /* number of blocks in warm sub-chain       */
   ulong cnt_for_resize_op;       /* counter to block resize operation        */
@@ -65,6 +66,7 @@ typedef struct st_key_cache
   HASH_LINK **hash_root;         /* arr. of entries into hash table buckets  */
   HASH_LINK *hash_link_root;     /* memory for hash table links              */
   HASH_LINK *free_hash_list;     /* list of free hash links                  */
+  BLOCK_LINK *free_block_list; /* list of free blocks */
   BLOCK_LINK *block_root;        /* memory for block links                   */
   byte HUGE_PTR *block_mem;      /* memory for block buffers                 */
   BLOCK_LINK *used_last;         /* ptr to the last block of the LRU chain   */
@@ -87,7 +89,6 @@ typedef struct st_key_cache
   ulong param_age_threshold;    /* determines when hot block is downgraded  */
 
   /* Statistics variables */
-  ulong global_blocks_used;	/* number of currently used blocks          */
   ulong global_blocks_changed;	/* number of currently dirty blocks         */
   ulong global_cache_w_requests;/* number of write requests (write hits)    */
   ulong global_cache_write;     /* number of writes from the cache to files */
diff --git a/mysql-test/r/key_cache.result b/mysql-test/r/key_cache.result
index b83a226776d..eeb838dc72c 100644
--- a/mysql-test/r/key_cache.result
+++ b/mysql-test/r/key_cache.result
@@ -85,6 +85,14 @@ select @@key_cache_block_size;
 set global keycache1.key_buffer_size=1024*1024;
 create table t1 (p int primary key, a char(10)) delay_key_write=1;
 create table t2 (p int primary key, i int, a char(10), key k1(i), key k2(a));
+show status like 'key_blocks_used';
+Variable_name	Value
+Key_blocks_used	0
+show status like 'The below may fail on 64-bit systems (ingo)';
+Variable_name	Value
+show status like 'key_blocks_unused';
+Variable_name	Value
+Key_blocks_unused	1812
 insert into t1 values (1, 'qqqq'), (11, 'yyyy');
 insert into t2 values (1, 1, 'qqqq'), (2, 1, 'pppp'),
 (3, 1, 'yyyy'), (4, 3, 'zzzz');
@@ -100,6 +108,14 @@ p	i	a
 4	3	zzzz
 update t1 set p=2 where p=1;
 update t2 set i=2 where i=1;
+show status like 'key_blocks_used';
+Variable_name	Value
+Key_blocks_used	4
+show status like 'The below may fail on 64-bit systems (ingo)';
+Variable_name	Value
+show status like 'key_blocks_unused';
+Variable_name	Value
+Key_blocks_unused	1808
 cache index t1 key (`primary`) in keycache1;
 Table	Op	Msg_type	Msg_text
 test.t1	assign_to_keycache	status	OK
@@ -256,6 +272,14 @@ Table	Op	Msg_type	Msg_text
 test.t1	assign_to_keycache	status	OK
 test.t2	assign_to_keycache	status	OK
 drop table t1,t2,t3;
+show status like 'key_blocks_used';
+Variable_name	Value
+Key_blocks_used	4
+show status like 'The below may fail on 64-bit systems (ingo)';
+Variable_name	Value
+show status like 'key_blocks_unused';
+Variable_name	Value
+Key_blocks_unused	1812
 set global keycache2.key_buffer_size=0;
 set global keycache3.key_buffer_size=100;
 set global keycache3.key_buffer_size=0;
diff --git a/mysql-test/t/key_cache.test b/mysql-test/t/key_cache.test
index 2bf5cdbcf6f..ca846a31def 100644
--- a/mysql-test/t/key_cache.test
+++ b/mysql-test/t/key_cache.test
@@ -66,6 +66,10 @@ set global keycache1.key_buffer_size=1024*1024;
 create table t1 (p int primary key, a char(10)) delay_key_write=1;
 create table t2 (p int primary key, i int, a char(10), key k1(i), key k2(a));
 
+show status like 'key_blocks_used';
+show status like 'The below may fail on 64-bit systems (ingo)';
+show status like 'key_blocks_unused'; # This may fail on 64-bit systems (ingo)
+
 insert into t1 values (1, 'qqqq'), (11, 'yyyy');
 insert into t2 values (1, 1, 'qqqq'), (2, 1, 'pppp'),
                       (3, 1, 'yyyy'), (4, 3, 'zzzz');
@@ -75,6 +79,10 @@ select * from t2;
 update t1 set p=2 where p=1;
 update t2 set i=2 where i=1;
 
+show status like 'key_blocks_used';
+show status like 'The below may fail on 64-bit systems (ingo)';
+show status like 'key_blocks_unused'; # This may fail on 64-bit systems (ingo)
+
 cache index t1 key (`primary`) in keycache1;
 
 explain select p from t1;
@@ -133,6 +141,10 @@ cache index t3 in keycache2;
 cache index t1,t2 in default;
 drop table t1,t2,t3;
 
+show status like 'key_blocks_used';
+show status like 'The below may fail on 64-bit systems (ingo)';
+show status like 'key_blocks_unused'; # This may fail on 64-bit systems (ingo)
+
 # Cleanup
 # We don't reset keycache2 as we want to ensure that mysqld will reset it
 set global keycache2.key_buffer_size=0;
diff --git a/mysys/mf_keycache.c b/mysys/mf_keycache.c
index 689391537f8..168483f276b 100644
--- a/mysys/mf_keycache.c
+++ b/mysys/mf_keycache.c
@@ -20,6 +20,23 @@
   One cache can handle many files.
   It must contain buffers of the same blocksize.
   init_key_cache() should be used to init cache handler.
+
+  The free list (free_block_list) is a stack like structure.
+  When a block is freed by free_block(), it is pushed onto the stack.
+  When a new block is required it is first tried to pop one from the stack.
+  If the stack is empty, it is tried to get a never-used block from the pool.
+  If this is empty too, then a block is taken from the LRU ring, flushing it
+  to disk, if neccessary. This is handled in find_key_block().
+  With the new free list, the blocks can have three temperatures:
+  hot, warm and cold (which is free). This is remembered in the block header
+  by the enum BLOCK_TEMPERATURE temperature variable. Remembering the 
+  temperature is neccessary to correctly count the number of warm blocks, 
+  which is required to decide when blocks are allowed to become hot. Whenever 
+  a block is inserted to another (sub-)chain, we take the old and new 
+  temperature into account to decide if we got one more or less warm block.
+  blocks_unused is the sum of never used blocks in the pool and of currently
+  free blocks. blocks_used is the number of blocks fetched from the pool and
+  as such gives the maximum number of in-use blocks at any time.
 */
 
 #include "mysys_priv.h"
@@ -116,6 +133,9 @@ struct st_hash_link
 #define PAGE_TO_BE_READ         1
 #define PAGE_WAIT_TO_BE_READ    2
 
+/* block temperature determines in which (sub-)chain the block currently is */
+enum BLOCK_TEMPERATURE { BLOCK_COLD /*free*/ , BLOCK_WARM , BLOCK_HOT };
+
 /* key cache block */
 struct st_block_link
 {
@@ -130,6 +150,7 @@ struct st_block_link
   uint offset;            /* beginning of modified data in the buffer        */
   uint length;            /* end of data in the buffer                       */
   uint status;            /* state of the block                              */
+  enum BLOCK_TEMPERATURE temperature; /* block temperature: cold, warm, hot */
   uint hits_left;         /* number of hits left until promotion             */
   ulonglong last_hit_time; /* timestamp of the last hit                      */
   KEYCACHE_CONDVAR *condvar; /* condition variable for 'no readers' event    */
@@ -340,6 +361,7 @@ int init_key_cache(KEY_CACHE *keycache, uint key_cache_block_size,
       }
       blocks= blocks / 4*3;
     }
+    keycache->blocks_unused= (ulong) blocks;
     keycache->disk_blocks= (int) blocks;
     keycache->hash_links= hash_links;
     keycache->hash_root= (HASH_LINK**) ((char*) keycache->block_root +
@@ -357,12 +379,13 @@ int init_key_cache(KEY_CACHE *keycache, uint key_cache_block_size,
     keycache->free_hash_list= NULL;
     keycache->blocks_used= keycache->blocks_changed= 0;
 
-    keycache->global_blocks_used= keycache->global_blocks_changed= 0;
+    keycache->global_blocks_changed= 0;
     keycache->blocks_available=0;		/* For debugging */
 
     /* The LRU chain is empty after initialization */
     keycache->used_last= NULL;
     keycache->used_ins= NULL;
+    keycache->free_block_list= NULL;
     keycache->keycache_time= 0;
     keycache->warm_blocks= 0;
     keycache->min_warm_blocks= (division_limit ?
@@ -596,7 +619,7 @@ void end_key_cache(KEY_CACHE *keycache, my_bool cleanup)
   DBUG_PRINT("status",
     ("used: %d  changed: %d  w_requests: %ld  \
 writes: %ld  r_requests: %ld  reads: %ld",
-      keycache->global_blocks_used, keycache->global_blocks_changed,
+      keycache->blocks_used, keycache->global_blocks_changed,
       keycache->global_cache_w_requests, keycache->global_cache_write,
       keycache->global_cache_r_requests, keycache->global_cache_read));
 
@@ -1014,7 +1037,9 @@ static inline void unreg_request(KEY_CACHE *keycache,
       keycache->warm_blocks > keycache->min_warm_blocks;
     if (hot)
     {
-      keycache->warm_blocks--;
+      if (block->temperature == BLOCK_WARM)
+        keycache->warm_blocks--;
+      block->temperature= BLOCK_HOT;
       KEYCACHE_DBUG_PRINT("unreg_request", ("#warm_blocks=%u",
                            keycache->warm_blocks));
     }
@@ -1026,7 +1051,11 @@ static inline void unreg_request(KEY_CACHE *keycache,
       block= keycache->used_ins;
       unlink_block(keycache, block);
       link_block(keycache, block, 0, 0);
-      keycache->warm_blocks++;
+      if (block->temperature != BLOCK_WARM)
+      {
+        keycache->warm_blocks++;
+        block->temperature= BLOCK_WARM;
+      }
       KEYCACHE_DBUG_PRINT("unreg_request", ("#warm_blocks=%u",
                            keycache->warm_blocks));
     }
@@ -1363,28 +1392,40 @@ restart:
     if (! block)
     {
       /* No block is assigned for the page yet */
-      if (keycache->blocks_used < (uint) keycache->disk_blocks)
+      if (keycache->blocks_unused)
       {
-	/* There are some never used blocks, take first of them */
-        hash_link->block= block= &keycache->block_root[keycache->blocks_used];
-        block->buffer= ADD_TO_PTR(keycache->block_mem,
-                                  ((ulong) keycache->blocks_used*
-                                   keycache->key_cache_block_size),
-                                  byte*);
+        if (keycache->free_block_list)
+        {
+          /* There is a block in the free list. */
+          block= keycache->free_block_list;
+          keycache->free_block_list= block->next_used;
+          block->next_used= NULL;
+        }
+        else
+        {
+          /* There are some never used blocks, take first of them */
+          block= &keycache->block_root[keycache->blocks_used];
+          block->buffer= ADD_TO_PTR(keycache->block_mem,
+                                    ((ulong) keycache->blocks_used*
+                                     keycache->key_cache_block_size),
+                                    byte*);
+          keycache->blocks_used++;
+        }
+        keycache->blocks_unused--;
         block->status= 0;
         block->length= 0;
         block->offset= keycache->key_cache_block_size;
         block->requests= 1;
-        keycache->blocks_used++;
-	keycache->global_blocks_used++;
-        keycache->warm_blocks++;
+        block->temperature= BLOCK_COLD;
         block->hits_left= init_hits_left;
         block->last_hit_time= 0;
         link_to_file_list(keycache, block, file, 0);
         block->hash_link= hash_link;
+        hash_link->block= block;
         page_status= PAGE_TO_BE_READ;
         KEYCACHE_DBUG_PRINT("find_key_block",
-                            ("got never used block %u", BLOCK_NUMBER(block)));
+                            ("got free or never used block %u",
+                             BLOCK_NUMBER(block)));
       }
       else
       {
@@ -2021,7 +2062,7 @@ end:
 /*
   Free block: remove reference to it from hash table,
   remove it from the chain file of dirty/clean blocks
-  and add it at the beginning of the LRU chain
+  and add it to the free list.
 */
 
 static void free_block(KEY_CACHE *keycache, BLOCK_LINK *block)
@@ -2045,6 +2086,17 @@ static void free_block(KEY_CACHE *keycache, BLOCK_LINK *block)
                       ("block is freed"));
   unreg_request(keycache, block, 0);
   block->hash_link= NULL;
+
+  /* Remove the free block from the LRU ring. */
+  unlink_block(keycache, block);
+  if (block->temperature == BLOCK_WARM)
+    keycache->warm_blocks--;
+  block->temperature= BLOCK_COLD;
+  /* Insert the free block in the free list. */
+  block->next_used= keycache->free_block_list;
+  keycache->free_block_list= block;
+  /* Keep track of the number of currently unused blocks. */
+  keycache->blocks_unused++;
 }
 
 
diff --git a/sql/mysqld.cc b/sql/mysqld.cc
index 3ae5914b439..85f6e4853bb 100644
--- a/sql/mysqld.cc
+++ b/sql/mysqld.cc
@@ -4839,8 +4839,10 @@ struct show_var_st status_vars[]= {
   {"Handler_discover",         (char*) &ha_discover_count,      SHOW_LONG},
   {"Key_blocks_not_flushed",   (char*) &dflt_key_cache_var.global_blocks_changed,
    SHOW_KEY_CACHE_LONG},
-  {"Key_blocks_used",          (char*) &dflt_key_cache_var.global_blocks_used,
-   SHOW_KEY_CACHE_LONG},
+  {"Key_blocks_used",          (char*) &dflt_key_cache_var.blocks_used,
+   SHOW_KEY_CACHE_CONST_LONG},
+  {"Key_blocks_unused",        (char*) &dflt_key_cache_var.blocks_unused,
+   SHOW_KEY_CACHE_CONST_LONG},
   {"Key_read_requests",        (char*) &dflt_key_cache_var.global_cache_r_requests,
    SHOW_KEY_CACHE_LONG},
   {"Key_reads",                (char*) &dflt_key_cache_var.global_cache_read,
diff --git a/sql/sql_show.cc b/sql/sql_show.cc
index 448dc825a26..51b44af63ec 100644
--- a/sql/sql_show.cc
+++ b/sql/sql_show.cc
@@ -2021,6 +2021,7 @@ int mysqld_show(THD *thd, const char *wild, show_var_st *variables,
 
 #endif /* HAVE_OPENSSL */
       case SHOW_KEY_CACHE_LONG:
+      case SHOW_KEY_CACHE_CONST_LONG:
 	value= (value-(char*) &dflt_key_cache_var)+ (char*) sql_key_cache;
 	end= int10_to_str(*(long*) value, buff, 10);
         break;
diff --git a/sql/sql_test.cc b/sql/sql_test.cc
index cc3d30e5983..d992c93f8fc 100644
--- a/sql/sql_test.cc
+++ b/sql/sql_test.cc
@@ -329,7 +329,7 @@ reads:          %10lu\n\n",
 	   name,
 	   (ulong) key_cache->param_buff_size, key_cache->param_block_size,
 	   key_cache->param_division_limit, key_cache->param_age_threshold,
-	   key_cache->global_blocks_used,key_cache->global_blocks_changed,
+	   key_cache->blocks_used,key_cache->global_blocks_changed,
 	   key_cache->global_cache_w_requests,key_cache->global_cache_write,
 	   key_cache->global_cache_r_requests,key_cache->global_cache_read);
   }
diff --git a/sql/structs.h b/sql/structs.h
index 86d754f00d6..d9607b220f7 100644
--- a/sql/structs.h
+++ b/sql/structs.h
@@ -183,7 +183,8 @@ enum SHOW_TYPE
   SHOW_SSL_CTX_SESS_TIMEOUTS, SHOW_SSL_CTX_SESS_CACHE_FULL,
   SHOW_SSL_GET_CIPHER_LIST,
 #endif /* HAVE_OPENSSL */
-  SHOW_RPL_STATUS, SHOW_SLAVE_RUNNING, SHOW_KEY_CACHE_LONG
+  SHOW_RPL_STATUS, SHOW_SLAVE_RUNNING,
+  SHOW_KEY_CACHE_LONG, SHOW_KEY_CACHE_CONST_LONG
 };
 
 enum SHOW_COMP_OPTION { SHOW_OPTION_YES, SHOW_OPTION_NO, SHOW_OPTION_DISABLED};