diff -cNr a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
*** a/src/backend/commands/vacuumlazy.c	2014-03-18 16:34:33.630514689 +1100
--- b/src/backend/commands/vacuumlazy.c	2014-03-19 11:27:30.048419316 +1100
***************
*** 95,100 ****
--- 95,107 ----
   */
  #define SKIP_PAGES_THRESHOLD	((BlockNumber) 32)
  
+ /*
+  * The following two values are used for identifying whether any table contains
+  * large number of unused rows and are eligible for 'VACUUM FULL'.
+  */
+ #define RELPAGES_VALUES_THRESHOLD 1000
+ #define FREESPACE_PERCENTAGE_THRESHOLD 0.5
+ 
  typedef struct LVRelStats
  {
  	/* hasindex = true means two-pass strategy; false means one-pass */
***************
*** 189,194 ****
--- 196,202 ----
  	double		new_live_tuples;
  	TransactionId new_frozen_xid;
  	MultiXactId new_min_multi;
+ 	Size total_freespace;
  
  	/* measure elapsed time iff autovacuum logging requires it */
  	if (IsAutoVacuumWorkerProcess() && Log_autovacuum_min_duration >= 0)
***************
*** 268,274 ****
  		lazy_truncate_heap(onerel, vacrelstats);
  
  	/* Vacuum the Free Space Map */
! 	FreeSpaceMapVacuum(onerel);
  
  	/*
  	 * Update statistics in pg_class.
--- 276,282 ----
  		lazy_truncate_heap(onerel, vacrelstats);
  
  	/* Vacuum the Free Space Map */
! 	total_freespace = FreeSpaceMapVacuum(onerel);
  
  	/*
  	 * Update statistics in pg_class.
***************
*** 343,348 ****
--- 351,357 ----
  			ereport(LOG,
  					(errmsg("automatic vacuum of table \"%s.%s.%s\": index scans: %d\n"
  							"pages: %d removed, %d remain\n"
+ 							"approximate free space: %zu\n"
  							"tuples: %.0f removed, %.0f remain, %.0f are dead but not yet removable\n"
  							"buffer usage: %d hits, %d misses, %d dirtied\n"
  					  "avg read rate: %.3f MB/s, avg write rate: %.3f MB/s\n"
***************
*** 353,358 ****
--- 362,368 ----
  							vacrelstats->num_index_scans,
  							vacrelstats->pages_removed,
  							vacrelstats->rel_pages,
+ 							total_freespace,
  							vacrelstats->tuples_deleted,
  							vacrelstats->new_rel_tuples,
  							vacrelstats->new_dead_tuples,
***************
*** 363,368 ****
--- 373,390 ----
  							pg_rusage_show(&ru0))));
  		}
  	}
+ 
+ 	/*
+ 	 * According the percentage of the free space in the table to check
+ 	 * whether a table contains large numbers of unused row and are eligible for
+ 	 * 'VACUUM FULL'
+ 	 */
+ 	if (vacrelstats->rel_pages > RELPAGES_VALUES_THRESHOLD &&
+ 		(total_freespace > vacrelstats->rel_pages * BLCKSZ * FREESPACE_PERCENTAGE_THRESHOLD))
+ 		ereport(LOG,
+ 				(errmsg("Table \"%s\" contains large numbers of unused row, suggest using VACUUM FULL on it!",
+ 						RelationGetRelationName(onerel))));
+ 
  }
  
  /*
diff -cNr a/src/backend/storage/freespace/freespace.c b/src/backend/storage/freespace/freespace.c
*** a/src/backend/storage/freespace/freespace.c	2014-03-18 16:34:33.690514688 +1100
--- b/src/backend/storage/freespace/freespace.c	2014-03-19 11:48:05.319417472 +1100
***************
*** 108,114 ****
  static int fsm_set_and_search(Relation rel, FSMAddress addr, uint16 slot,
  				   uint8 newValue, uint8 minValue);
  static BlockNumber fsm_search(Relation rel, uint8 min_cat);
! static uint8 fsm_vacuum_page(Relation rel, FSMAddress addr, bool *eof);
  
  
  /******** Public API ********/
--- 108,114 ----
  static int fsm_set_and_search(Relation rel, FSMAddress addr, uint16 slot,
  				   uint8 newValue, uint8 minValue);
  static BlockNumber fsm_search(Relation rel, uint8 min_cat);
! static uint8 fsm_vacuum_page(Relation rel, FSMAddress addr, bool *eof, Size *total_freespace);
  
  
  /******** Public API ********/
***************
*** 315,330 ****
  /*
   * FreeSpaceMapVacuum - scan and fix any inconsistencies in the FSM
   */
! void
  FreeSpaceMapVacuum(Relation rel)
  {
  	bool		dummy;
  
  	/*
  	 * Traverse the tree in depth-first order. The tree is stored physically
  	 * in depth-first order, so this should be pretty I/O efficient.
  	 */
! 	fsm_vacuum_page(rel, FSM_ROOT_ADDRESS, &dummy);
  }
  
  /******** Internal routines ********/
--- 315,332 ----
  /*
   * FreeSpaceMapVacuum - scan and fix any inconsistencies in the FSM
   */
! Size
  FreeSpaceMapVacuum(Relation rel)
  {
  	bool		dummy;
+ 	Size		total_freespace = 0;
  
  	/*
  	 * Traverse the tree in depth-first order. The tree is stored physically
  	 * in depth-first order, so this should be pretty I/O efficient.
  	 */
! 	fsm_vacuum_page(rel, FSM_ROOT_ADDRESS, &dummy, &total_freespace);
! 	return total_freespace;
  }
  
  /******** Internal routines ********/
***************
*** 725,735 ****
   * Recursive guts of FreeSpaceMapVacuum
   */
  static uint8
! fsm_vacuum_page(Relation rel, FSMAddress addr, bool *eof_p)
  {
  	Buffer		buf;
  	Page		page;
  	uint8		max_avail;
  
  	/* Read the page if it exists, or return EOF */
  	buf = fsm_readbuf(rel, addr, false);
--- 727,738 ----
   * Recursive guts of FreeSpaceMapVacuum
   */
  static uint8
! fsm_vacuum_page(Relation rel, FSMAddress addr, bool *eof_p, Size *total_freespace)
  {
  	Buffer		buf;
  	Page		page;
  	uint8		max_avail;
+ 	int			slot;
  
  	/* Read the page if it exists, or return EOF */
  	buf = fsm_readbuf(rel, addr, false);
***************
*** 749,755 ****
  	 */
  	if (addr.level > FSM_BOTTOM_LEVEL)
  	{
- 		int			slot;
  		bool		eof = false;
  
  		for (slot = 0; slot < SlotsPerFSMPage; slot++)
--- 752,757 ----
***************
*** 760,766 ****
  
  			/* After we hit end-of-file, just clear the rest of the slots */
  			if (!eof)
! 				child_avail = fsm_vacuum_page(rel, fsm_get_child(addr, slot), &eof);
  			else
  				child_avail = 0;
  
--- 762,768 ----
  
  			/* After we hit end-of-file, just clear the rest of the slots */
  			if (!eof)
! 				child_avail = fsm_vacuum_page(rel, fsm_get_child(addr, slot), &eof, total_freespace);
  			else
  				child_avail = 0;
  
***************
*** 774,779 ****
--- 776,788 ----
  			}
  		}
  	}
+ 	else if (addr.level == FSM_BOTTOM_LEVEL)
+ 	{
+ 		for (slot = 0; slot < SlotsPerFSMPage; slot++)
+ 		{
+ 			*total_freespace += fsm_space_cat_to_avail(fsm_get_avail(page,slot));
+ 		}
+ 	}
  
  	max_avail = fsm_get_max_avail(BufferGetPage(buf));
  
diff -cNr a/src/include/storage/freespace.h b/src/include/storage/freespace.h
*** a/src/include/storage/freespace.h	2014-03-18 16:34:33.579514686 +1100
--- b/src/include/storage/freespace.h	2014-03-18 16:34:44.093514674 +1100
***************
*** 31,36 ****
  							Size spaceAvail);
  
  extern void FreeSpaceMapTruncateRel(Relation rel, BlockNumber nblocks);
! extern void FreeSpaceMapVacuum(Relation rel);
  
  #endif   /* FREESPACE_H_ */
--- 31,36 ----
  							Size spaceAvail);
  
  extern void FreeSpaceMapTruncateRel(Relation rel, BlockNumber nblocks);
! extern Size FreeSpaceMapVacuum(Relation rel);
  
  #endif   /* FREESPACE_H_ */
