Merge pull request #3944 from pixelfed/staging

Add optional home feed caching
This commit is contained in:
daniel 2022-12-16 00:53:18 -07:00 committed by GitHub
commit b9daa3fb48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 196 additions and 15 deletions

View file

@ -8,6 +8,7 @@
- Server Directory ([#3762](https://github.com/pixelfed/pixelfed/pull/3762))
- Manually verify email address (php artisan user:verifyemail) ([682f5f0f](https://github.com/pixelfed/pixelfed/commit/682f5f0f))
- Manually generate in-app registration confirmation links (php artisan user:app-magic-link) ([73eb9e36](https://github.com/pixelfed/pixelfed/commit/73eb9e36))
- Optional home feed caching ([3328b367](https://github.com/pixelfed/pixelfed/commit/3328b367))
### Updates
- Update ApiV1Controller, include self likes in favourited_by endpoint ([58b331d2](https://github.com/pixelfed/pixelfed/commit/58b331d2))

View file

@ -61,6 +61,7 @@ use App\Jobs\VideoPipeline\{
use App\Services\{
AccountService,
BookmarkService,
CollectionService,
FollowerService,
InstanceService,
@ -768,6 +769,10 @@ class ApiV1Controller extends Controller
->whereFollowingId($target->id)
->delete();
if(config('instance.timeline.home.cached')) {
Cache::forget('pf:timelines:home:' . $user->profile_id);
}
FollowerService::remove($user->profile_id, $target->id);
$target->decrement('followers_count');
@ -1935,11 +1940,80 @@ class ApiV1Controller extends Controller
$limit = $request->input('limit') ?? 20;
$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');
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) {
$dir = $min ? '>' : '<';
$id = $min ?? $max;

View file

@ -454,29 +454,19 @@ class PublicApiController extends Controller
$user = $request->user();
$key = 'user:last_active_at:id:'.$user->id;
$ttl = now()->addMinutes(20);
Cache::remember($key, $ttl, function() use($user) {
if(Cache::get($key) == null) {
$user->last_active_at = now();
$user->save();
return;
});
Cache::put($key, true, 43200);
}
$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');
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) : [];
$types = ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'];
// $types = ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album', 'text'];
@ -496,6 +486,83 @@ class PublicApiController extends Controller
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) {
$dir = $min ? '>' : '<';
$id = $min ?? $max;

View file

@ -4,6 +4,7 @@ namespace App\Observers;
use App\Follower;
use App\Services\FollowerService;
use Cache;
class FollowerObserver
{
@ -15,6 +16,10 @@ class FollowerObserver
*/
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);
}

View file

@ -4,6 +4,7 @@ namespace App\Observers;
use App\Status;
use App\Services\ProfileStatusService;
use Cache;
class StatusObserver
{
@ -33,6 +34,10 @@ class StatusObserver
*/
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'])) {
ProfileStatusService::add($status->profile_id, $status->id);
}
@ -46,6 +51,10 @@ class StatusObserver
*/
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);
}

View file

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

View file

@ -164,7 +164,10 @@ class RestrictedNames
'explore',
'export',
'exports',
'external',
'f',
'fedi',
'fediverse',
'feed',
'featured',
'font',
@ -175,10 +178,12 @@ class RestrictedNames
'follow-me',
'follow_me',
'g',
'go',
'gdpr',
'graph',
'ghost',
'ghosts',
'global',
'group',
'groups',
'h',
@ -217,7 +222,10 @@ class RestrictedNames
'lab',
'labs',
'legal',
'link',
'live',
'look',
'look-back',
'loop',
'loops',
'location',
@ -226,6 +234,8 @@ class RestrictedNames
'logout',
'm',
'media',
'mini',
'micro',
'menu',
'music',
'my2020',
@ -239,6 +249,7 @@ class RestrictedNames
'my2028',
'my2029',
'my2030',
'my',
'n',
'news',
'new',
@ -249,6 +260,8 @@ class RestrictedNames
'newsrooms',
'news-room',
'news-rooms',
'network',
'networks',
'o',
'oauth',
'official',
@ -262,6 +275,8 @@ class RestrictedNames
'password',
'portfolio',
'portfolios',
'pre',
'post',
'privacy',
'private',
'q',
@ -273,6 +288,7 @@ class RestrictedNames
'register',
'registers',
'review',
'reviews',
'reset',
'report',
'results',
@ -328,6 +344,8 @@ class RestrictedNames
'www',
'x',
'y',
'year',
'year-in-review',
'z',
'400',
'401',

View file

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