Update FollowerService, use redis sorted sets for following relations

This commit is contained in:
Daniel Supernault 2022-12-06 23:41:05 -07:00
parent 80acafc67a
commit f46b01af51
No known key found for this signature in database
GPG Key ID: 0DEF1C662C9033F7
4 changed files with 166 additions and 21 deletions

View File

@ -138,6 +138,7 @@ class DeleteAccountPipeline implements ShouldQueue
FollowerService::remove($follow->profile_id, $follow->following_id);
$follow->delete();
});
FollowerService::delCache($id);
Like::whereProfileId($id)->forceDelete();
});

View File

@ -0,0 +1,87 @@
<?php
namespace App\Jobs\FollowPipeline;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Services\AccountService;
use App\Services\FollowerService;
use Cache;
use DB;
use App\Profile;
class FollowServiceWarmCache implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $profileId;
public $tries = 5;
public $timeout = 300;
public $failOnTimeout = true;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($profileId)
{
$this->profileId = $profileId;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$id = $this->profileId;
$account = AccountService::get($id, true);
if(!$account) {
Cache::put(FollowerService::FOLLOWERS_SYNC_KEY . $id, 1);
Cache::put(FollowerService::FOLLOWING_SYNC_KEY . $id, 1);
return;
}
DB::table('followers')
->select('id', 'following_id', 'profile_id')
->whereFollowingId($id)
->orderBy('id')
->chunk(200, function($followers) use($id) {
foreach($followers as $follow) {
FollowerService::add($follow->profile_id, $id);
}
});
DB::table('followers')
->select('id', 'following_id', 'profile_id')
->whereProfileId($id)
->orderBy('id')
->chunk(200, function($followers) use($id) {
foreach($followers as $follow) {
FollowerService::add($id, $follow->following_id);
}
});
Cache::put(FollowerService::FOLLOWERS_SYNC_KEY . $id, 1);
Cache::put(FollowerService::FOLLOWING_SYNC_KEY . $id, 1);
$profile = Profile::find($id);
if($profile) {
$profile->following_count = DB::table('followers')->whereProfileId($id)->count();
$profile->followers_count = DB::table('followers')->whereFollowingId($id)->count();
$profile->save();
}
AccountService::del($id);
return;
}
}

View File

@ -10,10 +10,12 @@ use App\{
Profile,
User
};
use App\Jobs\FollowPipeline\FollowServiceWarmCache;
class FollowerService
{
const CACHE_KEY = 'pf:services:followers:';
const FOLLOWERS_SYNC_ACTIVE = 'pf:services:followers:sync-active:';
const FOLLOWERS_SYNC_KEY = 'pf:services:followers:sync-followers:';
const FOLLOWING_SYNC_KEY = 'pf:services:followers:sync-following:';
const FOLLOWING_KEY = 'pf:services:follow:following:id:';
@ -38,19 +40,59 @@ class FollowerService
public static function followers($id, $start = 0, $stop = 10)
{
self::cacheSyncCheck($id, 'followers');
return Redis::zrange(self::FOLLOWERS_KEY . $id, $start, $stop);
return Redis::zrevrange(self::FOLLOWERS_KEY . $id, $start, $stop);
}
public static function following($id, $start = 0, $stop = 10)
{
self::cacheSyncCheck($id, 'following');
return Redis::zrange(self::FOLLOWING_KEY . $id, $start, $stop);
return Redis::zrevrange(self::FOLLOWING_KEY . $id, $start, $stop);
}
public static function followersPaginate($id, $page = 1, $limit = 10)
{
$start = $page == 1 ? 0 : $page * $limit - $limit;
$end = $start + ($limit - 1);
return self::followers($id, $start, $end);
}
public static function followingPaginate($id, $page = 1, $limit = 10)
{
$start = $page == 1 ? 0 : $page * $limit - $limit;
$end = $start + ($limit - 1);
return self::following($id, $start, $end);
}
public static function followerCount($id, $warmCache = true)
{
if($warmCache) {
self::cacheSyncCheck($id, 'followers');
}
return Redis::zCard(self::FOLLOWERS_KEY . $id);
}
public static function followingCount($id, $warmCache = true)
{
if($warmCache) {
self::cacheSyncCheck($id, 'following');
}
return Redis::zCard(self::FOLLOWING_KEY . $id);
}
public static function follows(string $actor, string $target)
{
self::cacheSyncCheck($target, 'followers');
return (bool) Redis::zScore(self::FOLLOWERS_KEY . $target, $actor);
if($actor == $target) {
return false;
}
if(self::followerCount($target, false) && self::followingCount($actor, false)) {
self::cacheSyncCheck($target, 'followers');
return (bool) Redis::zScore(self::FOLLOWERS_KEY . $target, $actor);
} else {
self::cacheSyncCheck($target, 'followers');
self::cacheSyncCheck($actor, 'following');
return Follower::whereProfileId($actor)->whereFollowingId($target)->exists();
}
}
public static function cacheSyncCheck($id, $scope = 'followers')
@ -59,21 +101,25 @@ class FollowerService
if(Cache::get(self::FOLLOWERS_SYNC_KEY . $id) != null) {
return;
}
$followers = Follower::whereFollowingId($id)->pluck('profile_id');
$followers->each(function($fid) use($id) {
self::add($fid, $id);
});
Cache::put(self::FOLLOWERS_SYNC_KEY . $id, 1, 604800);
if(Cache::get(self::FOLLOWERS_SYNC_ACTIVE . $id) != null) {
return;
}
FollowServiceWarmCache::dispatch($id)->onQueue('low');
Cache::put(self::FOLLOWERS_SYNC_ACTIVE . $id, 1, 604800);
}
if($scope === 'following') {
if(Cache::get(self::FOLLOWING_SYNC_KEY . $id) != null) {
return;
}
$followers = Follower::whereProfileId($id)->pluck('following_id');
$followers->each(function($fid) use($id) {
self::add($id, $fid);
});
Cache::put(self::FOLLOWING_SYNC_KEY . $id, 1, 604800);
if(Cache::get(self::FOLLOWERS_SYNC_ACTIVE . $id) != null) {
return;
}
FollowServiceWarmCache::dispatch($id)->onQueue('low');
Cache::put(self::FOLLOWERS_SYNC_ACTIVE . $id, 1, 604800);
}
return;
}
@ -149,7 +195,8 @@ class FollowerService
Redis::del(self::CACHE_KEY . $id);
Redis::del(self::FOLLOWING_KEY . $id);
Redis::del(self::FOLLOWERS_KEY . $id);
Redis::del(self::FOLLOWERS_SYNC_KEY . $id);
Redis::del(self::FOLLOWING_SYNC_KEY . $id);
Cache::forget(self::FOLLOWERS_SYNC_KEY . $id);
Cache::forget(self::FOLLOWING_SYNC_KEY . $id);
Cache::forget(self::FOLLOWERS_SYNC_ACTIVE . $id);
}
}

View File

@ -3,7 +3,9 @@
namespace App\Transformer\Api;
use Auth;
use Cache;
use App\Profile;
use App\User;
use League\Fractal;
use App\Services\PronounService;
@ -15,8 +17,16 @@ class AccountTransformer extends Fractal\TransformerAbstract
public function transform(Profile $profile)
{
$local = $profile->domain == null;
$is_admin = !$local ? false : $profile->user->is_admin;
if(!$profile) {
return [];
}
$adminIds = Cache::remember('pf:admin-ids', 604800, function() {
return User::whereIsAdmin(true)->pluck('profile_id')->toArray();
});
$local = $profile->private_key != null;
$is_admin = !$local ? false : in_array($profile->id, $adminIds);
$acct = $local ? $profile->username : substr($profile->username, 1);
$username = $local ? $profile->username : explode('@', $acct)[0];
return [
@ -26,9 +36,9 @@ class AccountTransformer extends Fractal\TransformerAbstract
'display_name' => $profile->name,
'discoverable' => true,
'locked' => (bool) $profile->is_private,
'followers_count' => (int) $profile->followerCount(),
'following_count' => (int) $profile->followingCount(),
'statuses_count' => (int) $profile->statusCount(),
'followers_count' => (int) $profile->followers_count,
'following_count' => (int) $profile->following_count,
'statuses_count' => (int) $profile->status_count,
'note' => $profile->bio ?? '',
'note_text' => $profile->bio ? strip_tags($profile->bio) : null,
'url' => $profile->url(),