Add optional home feed caching

This commit is contained in:
Daniel Supernault 2022-12-16 00:43:20 -07:00
parent a972671115
commit 3328b367fd
No known key found for this signature in database
GPG key ID: 0DEF1C662C9033F7
6 changed files with 177 additions and 15 deletions

View file

@ -61,6 +61,7 @@ use App\Jobs\VideoPipeline\{
use App\Services\{ use App\Services\{
AccountService, AccountService,
BookmarkService,
CollectionService, CollectionService,
FollowerService, FollowerService,
InstanceService, InstanceService,
@ -768,6 +769,10 @@ class ApiV1Controller extends Controller
->whereFollowingId($target->id) ->whereFollowingId($target->id)
->delete(); ->delete();
if(config('instance.timeline.home.cached')) {
Cache::forget('pf:timelines:home:' . $user->profile_id);
}
FollowerService::remove($user->profile_id, $target->id); FollowerService::remove($user->profile_id, $target->id);
$target->decrement('followers_count'); $target->decrement('followers_count');
@ -1935,11 +1940,80 @@ class ApiV1Controller extends Controller
$limit = $request->input('limit') ?? 20; $limit = $request->input('limit') ?? 20;
$pid = $request->user()->profile_id; $pid = $request->user()->profile_id;
$following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) { $following = Cache::remember('profile:following:'.$pid, 1209600, function() use($pid) {
$following = Follower::whereProfileId($pid)->pluck('following_id'); $following = Follower::whereProfileId($pid)->pluck('following_id');
return $following->push($pid)->toArray(); return $following->push($pid)->toArray();
}); });
if(config('instance.timeline.home.cached') && (!$min && !$max)) {
$ttl = config('instance.timeline.home.cache_ttl');
$res = Cache::remember(
'pf:timelines:home:' . $pid,
$ttl,
function() use(
$following,
$limit,
$pid
) {
return Status::select(
'id',
'uri',
'caption',
'rendered',
'profile_id',
'type',
'in_reply_to_id',
'reblog_of_id',
'is_nsfw',
'scope',
'local',
'reply_count',
'comments_disabled',
'place_id',
'likes_count',
'reblogs_count',
'created_at',
'updated_at'
)
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereIn('profile_id', $following)
->whereIn('visibility',['public', 'unlisted', 'private'])
->orderBy('created_at', 'desc')
->limit($limit)
->get()
->map(function($s) {
$status = StatusService::get($s->id, false);
if(!$status) {
return false;
}
return $status;
})
->filter(function($s) {
return $s && isset($s['account']['id']);
})
->values()
->toArray();
});
$res = collect($res)
->map(function($s) use ($pid) {
$status = StatusService::get($s['id'], false);
if(!$status) {
return false;
}
$status['favourited'] = (bool) LikeService::liked($pid, $s['id']);
$status['bookmarked'] = (bool) BookmarkService::get($pid, $s['id']);
$status['reblogged'] = (bool) ReblogService::get($pid, $s['id']);
return $status;
})
->filter(function($s) {
return $s && isset($s['account']['id']);
})
->values()
->take($limit)
->toArray();
}
if($min || $max) { if($min || $max) {
$dir = $min ? '>' : '<'; $dir = $min ? '>' : '<';
$id = $min ?? $max; $id = $min ?? $max;

View file

@ -454,29 +454,19 @@ class PublicApiController extends Controller
$user = $request->user(); $user = $request->user();
$key = 'user:last_active_at:id:'.$user->id; $key = 'user:last_active_at:id:'.$user->id;
$ttl = now()->addMinutes(20); if(Cache::get($key) == null) {
Cache::remember($key, $ttl, function() use($user) {
$user->last_active_at = now(); $user->last_active_at = now();
$user->save(); $user->save();
return; Cache::put($key, true, 43200);
}); }
$pid = $user->profile_id; $pid = $user->profile_id;
$following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) { $following = Cache::remember('profile:following:'.$pid, 1209600, function() use($pid) {
$following = Follower::whereProfileId($pid)->pluck('following_id'); $following = Follower::whereProfileId($pid)->pluck('following_id');
return $following->push($pid)->toArray(); return $following->push($pid)->toArray();
}); });
if($recentFeed == true) {
$key = 'profile:home-timeline-cursor:'.$user->id;
$ttl = now()->addMinutes(30);
$min = Cache::remember($key, $ttl, function() use($pid) {
$res = StatusView::whereProfileId($pid)->orderByDesc('status_id')->first();
return $res ? $res->status_id : null;
});
}
$filtered = $user ? UserFilterService::filters($user->profile_id) : []; $filtered = $user ? UserFilterService::filters($user->profile_id) : [];
$types = ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']; $types = ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'];
// $types = ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album', 'text']; // $types = ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album', 'text'];
@ -496,6 +486,83 @@ class PublicApiController extends Controller
array_push($types, 'poll'); array_push($types, 'poll');
} }
if(config('instance.timeline.home.cached') && $limit == 6 && (!$min && !$max)) {
$ttl = config('instance.timeline.home.cache_ttl');
$res = Cache::remember(
'pf:timelines:home:' . $pid,
$ttl,
function() use(
$types,
$textOnlyReplies,
$following,
$limit,
$filtered,
$user
) {
return Status::select(
'id',
'uri',
'caption',
'rendered',
'profile_id',
'type',
'in_reply_to_id',
'reblog_of_id',
'is_nsfw',
'scope',
'local',
'reply_count',
'comments_disabled',
'place_id',
'likes_count',
'reblogs_count',
'created_at',
'updated_at'
)
->whereIn('type', $types)
->when(!$textOnlyReplies, function($q, $textOnlyReplies) {
return $q->whereNull('in_reply_to_id');
})
->whereIn('profile_id', $following)
->whereIn('visibility',['public', 'unlisted', 'private'])
->orderBy('created_at', 'desc')
->limit($limit)
->get()
->map(function($s) use ($user) {
$status = StatusService::get($s->id, false);
if(!$status) {
return false;
}
return $status;
})
->filter(function($s) use($filtered) {
return $s && in_array($s['account']['id'], $filtered) == false;
})
->values()
->toArray();
});
$res = collect($res)
->map(function($s) use ($user) {
$status = StatusService::get($s['id'], false);
if(!$status) {
return false;
}
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s['id']);
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s['id']);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s['id']);
return $status;
})
->filter(function($s) use($filtered) {
return $s && in_array($s['account']['id'], $filtered) == false;
})
->values()
->take($limit)
->toArray();
return $res;
}
if($min || $max) { if($min || $max) {
$dir = $min ? '>' : '<'; $dir = $min ? '>' : '<';
$id = $min ?? $max; $id = $min ?? $max;

View file

@ -4,6 +4,7 @@ namespace App\Observers;
use App\Follower; use App\Follower;
use App\Services\FollowerService; use App\Services\FollowerService;
use Cache;
class FollowerObserver class FollowerObserver
{ {
@ -15,6 +16,10 @@ class FollowerObserver
*/ */
public function created(Follower $follower) public function created(Follower $follower)
{ {
if(config('instance.timeline.home.cached')) {
Cache::forget('pf:timelines:home:' . $follower->profile_id);
}
FollowerService::add($follower->profile_id, $follower->following_id); FollowerService::add($follower->profile_id, $follower->following_id);
} }

View file

@ -4,6 +4,7 @@ namespace App\Observers;
use App\Status; use App\Status;
use App\Services\ProfileStatusService; use App\Services\ProfileStatusService;
use Cache;
class StatusObserver class StatusObserver
{ {
@ -33,6 +34,10 @@ class StatusObserver
*/ */
public function updated(Status $status) public function updated(Status $status)
{ {
if(config('instance.timeline.home.cached')) {
Cache::forget('pf:timelines:home:' . $status->profile_id);
}
if(in_array($status->scope, ['public', 'unlisted']) && in_array($status->type, ['photo', 'photo:album', 'video'])) { if(in_array($status->scope, ['public', 'unlisted']) && in_array($status->type, ['photo', 'photo:album', 'video'])) {
ProfileStatusService::add($status->profile_id, $status->id); ProfileStatusService::add($status->profile_id, $status->id);
} }
@ -46,6 +51,10 @@ class StatusObserver
*/ */
public function deleted(Status $status) public function deleted(Status $status)
{ {
if(config('instance.timeline.home.cached')) {
Cache::forget('pf:timelines:home:' . $status->profile_id);
}
ProfileStatusService::delete($status->profile_id, $status->id); ProfileStatusService::delete($status->profile_id, $status->id);
} }

View file

@ -27,6 +27,7 @@ class FollowerService
RelationshipService::refresh($actor, $target); RelationshipService::refresh($actor, $target);
Redis::zadd(self::FOLLOWING_KEY . $actor, $ts, $target); Redis::zadd(self::FOLLOWING_KEY . $actor, $ts, $target);
Redis::zadd(self::FOLLOWERS_KEY . $target, $ts, $actor); Redis::zadd(self::FOLLOWERS_KEY . $target, $ts, $actor);
Cache::forget('profile:following:' . $actor);
} }
public static function remove($actor, $target) public static function remove($actor, $target)
@ -38,6 +39,7 @@ class FollowerService
AccountService::del($actor); AccountService::del($actor);
AccountService::del($target); AccountService::del($target);
RelationshipService::refresh($actor, $target); RelationshipService::refresh($actor, $target);
Cache::forget('profile:following:' . $actor);
} }
public static function followers($id, $start = 0, $stop = 10) public static function followers($id, $start = 0, $stop = 10)

View file

@ -23,6 +23,11 @@ return [
'email' => env('INSTANCE_CONTACT_EMAIL'), 'email' => env('INSTANCE_CONTACT_EMAIL'),
'timeline' => [ 'timeline' => [
'home' => [
'cached' => env('PF_HOME_TIMELINE_CACHE', false),
'cache_ttl' => env('PF_HOME_TIMELINE_CACHE_TTL', 900)
],
'local' => [ 'local' => [
'is_public' => env('INSTANCE_PUBLIC_LOCAL_TIMELINE', false) 'is_public' => env('INSTANCE_PUBLIC_LOCAL_TIMELINE', false)
], ],