For me I sent a separate table along with view_count column. If your data is mostly dynamic then you can use polymorphic. If not store the full url.
Lets say the proverbial blog and you want to count views on a article:
id - integer
user_id - integer
ip - string
views - integer //optional
viewable_id - integer
viewable_type - string
So a middleware is set to check if view count needs to be set and incremented. Fire an queued event or job to run it in the background.
$article = Article::with('views')->where('slug', $this->slug)->first();
if(auth()->check()) {
//see if it contains the user_id from auth()->id(), if found you can increment optional user view count, otherwise done
// if not found, insert new row and increment main model's view count
}
// otherwise guest, check IP in IP column, if found can in increment optional user view count, otherwise done
// insert new row with user_id set to 0 (means guest) increment main model's view count
Instead of always hitting the data base you could load the user's view history in a cache on login and check that, and if guest also start a cache when they hit a page that you want view counts. This way you can update both (if using events use two listeners, one for db and one to update cache).
This also gives you a few more data options. Which user has viewed an article the most. Which articles does a user like to view a lot etc (analytic data to keep your user's engaged and tailor articles towards that). You also will have a separate guest view count, which you can assume that more then likely they found the article via search or social media and went to your site to read it.
nothing will be 100% until IPv6 comes out, even then you can't control some one with 20 devices hitting the page 20 times, but it minimizes it. 20 is better then 200.