As I first reported an issue/feature request about general entity history, I realized that I actually would need it right now. So I proceeded to make a proof of concept in order to update my issue. But instead of updating the issue I figured that my post better belongs here in a forum where maybe more people could take part of it and I could get some more feedback.

Please see the background of this topic before continuing: #1029708: History table for any entity

First of all I took the system history table as a starting point and split the 'timestamp' column into 'first_read' and 'last_read' in order to determine if the entity has been seen since last update.

$schema['oddbit_entity_history'] = array(
  'description' => 'A record of which {users} have read which entities.',
  'fields' => array(
    'entity_type' => array(
      'description' => 'The viewed entity type.',
      'type' => 'varchar',
      'length' => 32,
      'not null' => TRUE,
      'default' => ''
    ),
    'eid' => array(
      'description' => 'The id of the entity.',
      'type' => 'int',
      'not null' => TRUE,
      'default' => 0,
    ),
    'uid' => array(
      'description' => 'The {users}.uid that read the entity id.',
      'type' => 'int',
      'not null' => TRUE,
      'default' => 0,
    ),
    'first_read' => array(
      'description' => 'The Unix timestamp at which the first read occurred.',
      'type' => 'int',
      'not null' => TRUE,
      'default' => 0,
    ),
    'last_read' => array(
      'description' => 'The Unix timestamp at which last read occured.',
      'type' => 'int',
      'not null' => TRUE,
      'default' => 0,
    ),
  ),
  'primary key' => array('entity_type', 'uid', 'eid'),
  'indexes' => array(
    'idx_oddbit_entity_history_id' => array('eid'),
    'idx_oddbit_entity_history_last_read' => array('last_read'),
  ),
  
  'foreign keys' => array(      
    'fk_entity_history_user' => array(
      'table' => 'users',
      'columns' => array('uid' => 'uid'),
    ),
  ),    
);

After that I added some of functions to get things done in an internal library module for misc tools.

function oddbit_entity_mark_read($entity_type, $entity, $account = NULL) {
  global $user;
  
  if (!isset($account) || !is_object($account)) {
    $account = $user;
  }
    
  list($eid, , ) = entity_extract_ids($entity_type, $entity);
  $last_read = oddbit_entity_get_last_read($entity_type, $entity, $account);
  
  $current_timestamp = REQUEST_TIME;
  $fields = array(
    'last_read' => $current_timestamp,
  );  
  
  // No previous record of reading this entity
  if(!$last_read) {
    $fields += array(
      'entity_type' => $entity_type,
      'eid' => $eid,
      'uid' => $account->uid,
      'first_read' => $current_timestamp,
    );
    
    // Record that the user read this entity
    db_insert('oddbit_entity_history')
      ->fields($fields)
      ->execute();
  }
  
  else {
    // Update the latest read for this entity
    db_update('oddbit_entity_history')
      ->fields($fields)
      ->condition('entity_type', $entity_type)
      ->condition('uid', $account->uid)
      ->condition('eid', $eid)
      ->execute();
  }
}

function oddbit_entity_get_last_read($entity_type, $entity, $account = NULL) {
  return _oddbit_entity_get_history_timestamp($entity_type, $entity, $account, 'last_read');
}


function oddbit_entity_get_first_read($entity_type, $entity, $account = NULL) {
  return _oddbit_entity_get_history_timestamp($entity_type, $entity, $account, 'first_read');
}

function _oddbit_entity_get_history_timestamp($entity_type, $entity, $account, $ts_type) {
  global $user;
  
  if (!isset($account) || !is_object($account)) {
    $account = $user;
  }
  
  list($eid, , ) = entity_extract_ids($entity_type, $entity);
    
  $timestamps = db_select('oddbit_entity_history', 'history')
    ->fields('history', array($ts_type))
    ->condition('entity_type', $entity_type)
    ->condition('eid', $eid)
    ->condition('uid', $account->uid)
    ->range(0, 1)
    ->execute()
    ->fetchCol();

  if (is_array($timestamps) && count($timestamps)) {
    return $timestamps[0];
  }
    
  return 0;
}

Finally I implemented the hook_node_view() for the module in order to be able skip the system history table completely in my own custom made modules.

function oddbit_node_view($node, $view_mode, $langcode) {
  // Use our own improved history table instead of the regular
  oddbit_entity_mark_read('node', $node);
}

Just copy/paste it all into your own module of choice and find/replace 'oddbit' with whatever your module is named.

... and it seems to work fine at first glance.