[livejournal] r20327: LJSUP-10147: Implement abstract interace...
Committer: vsukhanov
LJSUP-10147: Implement abstract interace for access to friends relationsU trunk/bin/worker/load-friends-gm A trunk/cgi-bin/LJ/RelationService/ A trunk/cgi-bin/LJ/RelationService/MysqlAPI.pm A trunk/cgi-bin/LJ/RelationService.pm U trunk/cgi-bin/LJ/User.pm
Modified: trunk/bin/worker/load-friends-gm
===================================================================
--- trunk/bin/worker/load-friends-gm 2011-10-14 13:17:37 UTC (rev 20326)
+++ trunk/bin/worker/load-friends-gm 2011-10-14 13:24:25 UTC (rev 20327)
@@ -9,6 +9,7 @@
use strict;
use lib "$ENV{LJHOME}/cgi-bin";
require 'ljlib.pl';
+use LJ::RelationService;
use LJ::NewWorker::Gearman;
use Storable;
@@ -26,10 +27,11 @@
my $uid = $args->{userid};
my $mask = $args->{mask};
- my $friends = LJ::_get_friends_db($uid, $mask);
+ my $u = LJ::load_userid($uid);
+ my @friends = LJ::RelationService->load_relation_destinations($u, mask => $mask, nogearman => 1);
# nfreeze friends hashref and return
- return Storable::nfreeze($friends);
+ return Storable::nfreeze(\@friends);
}
sub load_friend_friendof_uids {
@@ -39,9 +41,8 @@
my $uid = $args->{uid};
my $opts = $args->{opts};
- my $u = LJ::load_userid($uid);
+ my $u = LJ::load_userid($uid);
+ my @uids = LJ::RelationService->find_relation_destinations($u, %$opts, nogearman => 1);
- my @uids = $u->_friend_friendof_uids_do(%$opts);
-
return Storable::nfreeze(\@uids);
}
Added: trunk/cgi-bin/LJ/RelationService/MysqlAPI.pm
===================================================================
--- trunk/cgi-bin/LJ/RelationService/MysqlAPI.pm (rev 0)
+++ trunk/cgi-bin/LJ/RelationService/MysqlAPI.pm 2011-10-14 13:24:25 UTC (rev 20327)
@@ -0,0 +1,457 @@
+package LJ::RelationService::MysqlAPI;
+use strict;
+
+
+## friends
+sub find_relation_destinations {
+ my $class = shift;
+ my $u = shift;
+ my %opts = @_;
+ my $limit = $opts{limit} || 50000;
+ my $nogearman = $opts{nogearman} || 0;
+
+ my $uids = $class->_friend_friendof_uids($u,
+ %opts,
+ limit => $limit,
+ nogearman => $nogearman,
+ mode => "friends",
+ );
+ return @$uids;
+}
+
+## friendofs
+sub find_relation_sources {
+ my $class = shift;
+ my $u = shift;
+ my %opts = @_;
+ my $limit = $opts{limit} || 50000;
+ my $nogearman = $opts{nogearman} || 0;
+
+ my $uids = $class->_friend_friendof_uids($u,
+ %opts,
+ limit => $limit,
+ nogearman => $nogearman,
+ mode => "friendofs",
+ );
+ return @$uids;
+}
+
+## friends rows
+sub load_relation_destinations {
+ my $class = shift;
+ my $u = shift;
+ my %opts = @_;
+ my $limit = $opts{limit} || 50000;
+ my $nogearman = $opts{nogearman} || 0;
+
+ my $friends = $class->_get_friends($u,
+ %opts,
+ limit => $limit,
+ nogearman => $nogearman,
+ );
+ return undef unless $friends;
+ return @$friends;
+}
+
+## friendofs rows
+sub load_relation_sources {
+ my $class = shift;
+ my $u = shift;
+ my %opts = @_;
+ my $limit = $opts{limit} || 50000;
+ my $nogearman = $opts{nogearman} || 0;
+
+ my $friendofs = $class->_get_friendofs($u,
+ %opts,
+ limit => $limit,
+ nogearman => $nogearman,
+ );
+ return undef unless $friendofs;
+ return @$friendofs;
+
+}
+
+
+##
+## Private methods
+##
+
+# helper method since the logic for both friends and friendofs is so similar
+sub _friend_friendof_uids {
+ my $class = shift;
+ my $u = shift;
+ my %args = @_;
+
+ my $mode = $args{mode};
+ my $limit = $args{limit};
+ my $nogearman = $args{nogearma} || 0;
+
+ ## check cache first
+ my $res = $class->_load_friend_friendof_uids_from_memcache($u, $mode, $limit);
+ return $res if defined $res;
+
+ # call normally if no gearman/not wanted
+ my $gc = '';
+ return $class->_friend_friendof_uids_do($u, skip_memcached => 1, %args) # we've already checked memcached above
+ if $nogearman or
+ not (LJ::conf_test($LJ::LOADFRIENDS_USING_GEARMAN, $u->userid) and
+ $gc = LJ::gearman_client());
+
+ # invoke gearman
+ my @uids = ();
+ my $args = Storable::nfreeze({uid => $u->id, opts => \%args});
+ my $task = Gearman::Task->new("load_friend_friendof_uids", \$args,
+ {
+ uniq => join("-", $mode, $u->id, $limit),
+ on_complete => sub {
+ my $res = shift;
+ return unless $res;
+ my $uidsref = Storable::thaw($$res);
+ @uids = @{$uidsref || []};
+ }
+ });
+ my $ts = $gc->new_task_set();
+ $ts->add_task($task);
+ $ts->wait(timeout => 30); # 30 sec timeout
+
+ return \@uids;
+
+}
+
+
+# actually get friend/friendof uids, should not be called directly
+sub _friend_friendof_uids_do {
+ my $class = shift;
+ my $u = shift;
+ my %args = @_;
+
+ ## ATTENTION:
+ ## 'nolimit' option should not be used to generate
+ ## regular page.
+ ## Use it with care only for admin pages only.
+
+ my $limit = $args{limit} || 50000;
+ my $nolimit = $args{nolimit} ? 1 : 0; ## use it with care
+ my $mode = $args{mode};
+ my $skip_memcached = $args{skip_memcached};
+
+ $skip_memcached = 1 if $nolimit;
+
+ ## cache
+ unless ($skip_memcached){
+ my $res = $class->_load_friend_friendof_uids_from_memcache($u, $mode, $limit);
+ return $res if $res;
+ }
+
+ ## db
+ ## disable $limit if $nolimit requires it.
+ my $uids = $class->_load_friend_friendof_uids_from_db($u, $mode, $limit * (!$nolimit));
+
+ if (not $nolimit and $uids and @$uids){
+ ## do not cache if $nolimit option is in use,
+ ## because with disabled limit we might put in the cache
+ ## much more data than usually required.
+
+ # if the list of uids is greater than 950k
+ # -- slow but this definitely works
+ my $pack = pack("N*", $limit);
+ foreach (@$uids) {
+ last if length $pack > 1024*950;
+ $pack .= pack("N*", $_);
+ }
+
+ ## memcached
+ my $memkey = $class->_friend_friendof_uids_memkey($u, $mode);
+ LJ::MemCache::add($memkey, $pack, 3600);
+ }
+
+ return $uids;
+}
+
+sub _friend_friendof_uids_memkey {
+ my ($class, $u, $mode) = @_;
+ my $memkey;
+
+ if ($mode eq "friends") {
+ $memkey = [$u->id, "friends2:" . $u->id];
+ } elsif ($mode eq "friendofs") {
+ $memkey = [$u->id, "friendofs2:" . $u->id];
+ } else {
+ die "mode must either be 'friends' or 'friendofs'";
+ }
+
+ return $memkey;
+}
+
+sub _load_friend_friendof_uids_from_memcache {
+ my ($class, $u, $mode, $limit) = @_;
+
+ my $memkey = $class->_friend_friendof_uids_memkey($u, $mode);
+
+ if (my $pack = LJ::MemCache::get($memkey)) {
+ my ($slimit, @uids) = unpack("N*", $pack);
+ # value in memcache is good if stored limit (from last time)
+ # is >= the limit currently being requested. we just may
+ # have to truncate it to match the requested limit
+
+ if ($slimit >= $limit) {
+ @uids = @uids[0..$limit-1] if @uids > $limit;
+ return \@uids;
+ }
+
+ # value in memcache is also good if number of items is less
+ # than the stored limit... because then we know it's the full
+ # set that got stored, not a truncated version.
+ return \@uids if @uids < $slimit;
+ }
+
+ return undef;
+}
+
+## Attention: if 'limit' arg is omited, this method loads all userid from friends table.
+sub _load_friend_friendof_uids_from_db {
+ my $class = shift;
+ my $u = shift;
+ my $mode = shift;
+ my $limit = shift;
+
+ $limit = " LIMIT $limit" if $limit;
+
+ my $sql = '';
+ if ($mode eq 'friends'){
+ $sql = "SELECT friendid FROM friends WHERE userid=? $limit";
+ } elsif ($mode eq 'friendofs'){
+ $sql = "SELECT userid FROM friends WHERE friendid=? $limit";
+ } else {
+ die "mode must either be 'friends' or 'friendofs'";
+ }
+
+ my $dbh = LJ::get_db_reader();
+ my $uids = $dbh->selectcol_arrayref($sql, undef, $u->id);
+ return $uids;
+}
+
+
+
+##
+## loads rows from friends table
+##
+sub _get_friends {
+ my $class = shift;
+ my $u = shift;
+ my %args = @_;
+
+ my $mask = $args{mask};
+ my $memcache_only = $args{memcache_only};
+ my $force_db = $args{force_db};
+ my $nogearman = $args{nogearma} || 0;
+
+ return undef unless $u->userid;
+ return undef if $LJ::FORCE_EMPTY_FRIENDS{$u->userid};
+
+ unless ($force_db) {
+ my $memc = $class->_get_friends_memc($u->userid, $mask);
+ return $memc if $memc;
+ }
+ return {} if $memcache_only; # no friends
+
+ # nothing from memcache, select all rows from the
+ # database and insert those into memcache
+ # then return rows that matched the given groupmask
+
+ # no gearman/gearman not wanted
+ my $gc = undef;
+ return $class->_get_friends_db($u->userid, $mask)
+ if $nogearman or
+ not (LJ::conf_test($LJ::LOADFRIENDS_USING_GEARMAN, $u->userid) and $gc = LJ::gearman_client());
+
+ # invoke the gearman
+ my $friends;
+ my $arg = Storable::nfreeze({ userid => $u->userid, mask => $mask });
+ my $task = Gearman::Task->new("load_friends", \$arg,
+ {
+ uniq => $u->userid,
+ on_complete => sub {
+ my $res = shift;
+ return unless $res;
+ $friends = Storable::thaw($$res);
+ }
+ });
+
+ my $ts = $gc->new_task_set();
+ $ts->add_task($task);
+ $ts->wait(timeout => 30); # 30 sec timeout
+
+ return $friends;
+}
+
+sub _get_friends_memc {
+ my $class = shift;
+ my $userid = shift
+ or Carp::croak("no userid to _get_friends_db");
+ my $mask = shift;
+
+ # memcache data version
+ my $ver = 1;
+
+ my $packfmt = "NH6H6NC";
+ my $packlen = 15; # bytes
+
+ my @cols = qw(friendid fgcolor bgcolor groupmask showbydefault);
+
+ # first, check memcache
+ my $memkey = [$userid, "friends:$userid"];
+
+ my $memfriends = LJ::MemCache::get($memkey);
+ return undef unless $memfriends;
+
+ my %friends; # rows to be returned
+
+ # first byte of object is data version
+ # only version 1 is meaningful right now
+ my $memver = substr($memfriends, 0, 1, '');
+ return undef unless $memver == $ver;
+
+ # get each $packlen-byte row
+ while (length($memfriends) >= $packlen) {
+ my @row = unpack($packfmt, substr($memfriends, 0, $packlen, ''));
+
+ # don't add into %friends hash if groupmask doesn't match
+ next if $mask && ! ($row[3]+0 & $mask+0);
+
+ # add "#" to beginning of colors
+ $row[$_] = "\#$row[$_]" foreach 1..2;
+
+ # turn unpacked row into hashref
+ my $fid = $row[0];
+ my $idx = 1;
+ foreach my $col (@cols[1..$#cols]) {
+ $friends{$fid}->{$col} = $row[$idx];
+ $idx++;
+ }
+ }
+
+ # got from memcache, return
+ return \%friends;
+}
+
+sub _get_friends_db {
+ my $class = shift;
+
+ my $userid = shift
+ or Carp::croak("no userid to _get_friends_db");
+ my $mask = shift;
+
+ my $dbh = LJ::get_db_writer();
+
+ my $lockname = "get_friends:$userid";
+ my $release_lock = sub {
+ LJ::release_lock($dbh, "global", $lockname);
+ };
+
+ # get a lock
+ my $lock = LJ::get_lock($dbh, "global", $lockname);
+ return {} unless $lock;
+
+ # in lock, try memcache
+ my $memc = _get_friends_memc($userid, $mask);
+ if ($memc) {
+ $release_lock->();
+ return $memc;
+ }
+
+ # inside lock, but still not populated, query db
+
+ # memcache data info
+ my $ver = 1;
+ my $memkey = [$userid, "friends:$userid"];
+ my $packfmt = "NH6H6NC";
+ my $packlen = 15; # bytes
+
+ # columns we're selecting
+ my @cols = qw(friendid fgcolor bgcolor groupmask showbydefault);
+
+ my $mempack = $ver; # full packed string to insert into memcache, byte 1 is dversion
+ my %friends = (); # friends object to be returned, all groupmasks match
+
+ my $sth = $dbh->prepare("SELECT friendid, fgcolor, bgcolor, groupmask, showbydefault " .
+ "FROM friends WHERE userid=?");
+ $sth->execute($userid);
+ die $dbh->errstr if $dbh->err;
+ while (my @row = $sth->fetchrow_array) {
+
+ # convert color columns to hex
+ $row[$_] = sprintf("%06x", $row[$_]) foreach 1..2;
+
+ my $newpack = pack($packfmt, @row);
+ last if length($mempack) + length($newpack) > 950*1024;
+
+ $mempack .= $newpack;
+
+ # unless groupmask matches, skip adding to %friends
+ next if $mask && ! ($row[3]+0 & $mask+0);
+
+ # add "#" to beginning of colors
+ $row[$_] = "\#$row[$_]" foreach 1..2;
+
+ my $fid = $row[0];
+ my $idx = 1;
+ foreach my $col (@cols[1..$#cols]) {
+ $friends{$fid}->{$col} = $row[$idx];
+ $idx++;
+ }
+ }
+
+ LJ::MemCache::add($memkey, $mempack);
+
+ # finished with lock, release it
+ $release_lock->();
+
+ return \%friends;
+}
+
+
+## friendofs
+sub _get_friendofs {
+ my $class = shift;
+ my $u = shift;
+ my %args = @_;
+ my $skip_memcached = $args{skip_memcached};
+ my $limit = $args{limit};
+ my $nolimit = $args{nolimit};
+
+ ## ATTENTION:
+ ## 'nolimit' option should not be used to generate
+ ## regular page.
+ ## Use it with care only for admin pages only.
+
+ $limit = 0 if $nolimit;
+
+ # first, check memcache
+ my $memkey = [$u->userid, "friendofs:" . $u->userid];
+
+ unless ($skip_memcached) {
+ my $memfriendofs = LJ::MemCache::get($memkey);
+ return @$memfriendofs if $memfriendofs;
+ }
+
+ # nothing from memcache, select all rows from the
+ # database and insert those into memcache
+
+ my $dbh = LJ::get_db_writer();
+ my $limit = $limit ? '' : " LIMIT " . ($LJ::MAX_FRIENDOF_LOAD+1);
+ my $friendofs = $dbh->selectcol_arrayref
+ ("SELECT userid FROM friends WHERE friendid=? $limit",
+ undef, $u->userid) || [];
+ die $dbh->errstr if $dbh->err;
+
+ ## do not cache if $nolimit option is in use,
+ ## because with disabled limit we might put in the cache
+ ## much more data than usually required.
+ LJ::MemCache::add($memkey, $friendofs) unless $skip_memcached;
+
+ return @$friendofs;
+}
+
+
+1
Added: trunk/cgi-bin/LJ/RelationService.pm
===================================================================
--- trunk/cgi-bin/LJ/RelationService.pm (rev 0)
+++ trunk/cgi-bin/LJ/RelationService.pm 2011-10-14 13:24:25 UTC (rev 20327)
@@ -0,0 +1,43 @@
+package LJ::RelationService;
+use strict;
+
+use LJ::RelationService::MysqlAPI;
+
+
+sub relation_api {
+ my $class = shift;
+ my $u = shift;
+ return "LJ::RelationService::MysqlAPI";
+}
+
+
+## findRelationDestinations
+sub find_relation_destinations {
+ my $class = shift;
+ my $u = shift;
+
+ my $interface = $class->relation_api($u);
+ return $interface->find_relation_destinations($u, @_);
+
+}
+
+## findRelationSources
+sub find_relation_sources {
+ my $class = shift;
+ my $u = shift;
+
+ my $interface = $class->relation_api($u);
+ return $interface->find_relation_sources($u, @_);
+
+}
+
+sub load_relation_destinations {
+ my $class = shift;
+ my $u = shift;
+
+ my $interface = $class->relation_api($u);
+ return $interface->load_relation_destinations($u, @_);
+
+}
+
+1
Modified: trunk/cgi-bin/LJ/User.pm
===================================================================
--- trunk/cgi-bin/LJ/User.pm 2011-10-14 13:17:37 UTC (rev 20326)
+++ trunk/cgi-bin/LJ/User.pm 2011-10-14 13:24:25 UTC (rev 20327)
@@ -28,6 +28,7 @@
use LJ::TimeUtil;
use LJ::User::PropStorage;
use LJ::FileStore;
+use LJ::RelationService;
use Class::Autouse qw(
URI
@@ -4564,7 +4565,7 @@
my $limit = int(delete $args{limit}) || 50000;
Carp::croak("unknown option") if %args;
- return $u->_friend_friendof_uids(limit => $limit, mode => "friendofs");
+ return LJ::RelationService->find_relation_sources($u, limit => $limit);
}
# returns array of friend uids. by default, limited at 50,000 items.
@@ -4573,10 +4574,9 @@
my $limit = int(delete $args{limit}) || 50000;
Carp::croak("unknown option") if %args;
- return $u->_friend_friendof_uids(limit => $limit, mode => "friends");
+ return LJ::RelationService->find_relation_destinations($u, limit => $limit);
}
-
# helper method since the logic for both friends and friendofs is so similar
sub _friend_friendof_uids {
my $u = shift;
@@ -4615,6 +4615,7 @@
# actually get friend/friendof uids, should not be called directly
sub _friend_friendof_uids_do {
my ($u, %args) = @_;
+## method is also called from load-friends-gm worker.
my $limit = int(delete $args{limit}) || 50000;
my $mode = delete $args{mode};
@@ -8653,163 +8654,16 @@
return undef unless $userid;
return undef if $LJ::FORCE_EMPTY_FRIENDS{$userid};
- unless ($force) {
- my $memc = _get_friends_memc($userid, $mask);
- return $memc if $memc;
- }
- return {} if $memcache_only; # no friends
+ my $u = LJ::load_userid($userid);
- # nothing from memcache, select all rows from the
- # database and insert those into memcache
- # then return rows that matched the given groupmask
-
- # no gearman/gearman not wanted
- my $gc = LJ::gearman_client();
- return _get_friends_db($userid, $mask)
- unless $gc && LJ::conf_test($LJ::LOADFRIENDS_USING_GEARMAN, $userid);
-
- # invoke the gearman
- my $friends;
- my $arg = Storable::nfreeze({ userid => $userid, mask => $mask });
- my $task = Gearman::Task->new("load_friends", \$arg,
- {
- uniq => "$userid",
- on_complete => sub {
- my $res = shift;
- return unless $res;
- $friends = Storable::thaw($$res);
- }
- });
-
- my $ts = $gc->new_task_set();
- $ts->add_task($task);
- $ts->wait(timeout => 30); # 30 sec timeout
-
- return $friends;
+ return LJ::RelationService->load_relation_destinations(
+ $u, uuid => $uuid,
+ mask => $mask,
+ memcache_only => $memcache_only,
+ force_db => $force,
+ );
}
-sub _get_friends_memc {
- my $userid = shift
- or Carp::croak("no userid to _get_friends_db");
- my $mask = shift;
-
- # memcache data version
- my $ver = 1;
-
- my $packfmt = "NH6H6NC";
- my $packlen = 15; # bytes
-
- my @cols = qw(friendid fgcolor bgcolor groupmask showbydefault);
-
- # first, check memcache
- my $memkey = [$userid, "friends:$userid"];
-
- my $memfriends = LJ::MemCache::get($memkey);
- return undef unless $memfriends;
-
- my %friends; # rows to be returned
-
- # first byte of object is data version
- # only version 1 is meaningful right now
- my $memver = substr($memfriends, 0, 1, '');
- return undef unless $memver == $ver;
-
- # get each $packlen-byte row
- while (length($memfriends) >= $packlen) {
- my @row = unpack($packfmt, substr($memfriends, 0, $packlen, ''));
-
- # don't add into %friends hash if groupmask doesn't match
- next if $mask && ! ($row[3]+0 & $mask+0);
-
- # add "#" to beginning of colors
- $row[$_] = "\#$row[$_]" foreach 1..2;
-
- # turn unpacked row into hashref
- my $fid = $row[0];
- my $idx = 1;
- foreach my $col (@cols[1..$#cols]) {
- $friends{$fid}->{$col} = $row[$idx];
- $idx++;
- }
- }
-
- # got from memcache, return
- return \%friends;
-}
-
-sub _get_friends_db {
- my $userid = shift
- or Carp::croak("no userid to _get_friends_db");
- my $mask = shift;
-
- my $dbh = LJ::get_db_writer();
-
- my $lockname = "get_friends:$userid";
- my $release_lock = sub {
- LJ::release_lock($dbh, "global", $lockname);
- };
-
- # get a lock
- my $lock = LJ::get_lock($dbh, "global", $lockname);
- return {} unless $lock;
-
- # in lock, try memcache
- my $memc = _get_friends_memc($userid, $mask);
- if ($memc) {
- $release_lock->();
- return $memc;
- }
-
- # inside lock, but still not populated, query db
-
- # memcache data info
- my $ver = 1;
- my $memkey = [$userid, "friends:$userid"];
- my $packfmt = "NH6H6NC";
- my $packlen = 15; # bytes
-
- # columns we're selecting
- my @cols = qw(friendid fgcolor bgcolor groupmask showbydefault);
-
- my $mempack = $ver; # full packed string to insert into memcache, byte 1 is dversion
- my %friends; # friends object to be returned, all groupmasks match
-
- my $sth = $dbh->prepare("SELECT friendid, fgcolor, bgcolor, groupmask, showbydefault " .
- "FROM friends WHERE userid=?");
- $sth->execute($userid);
- die $dbh->errstr if $dbh->err;
- while (my @row = $sth->fetchrow_array) {
-
- # convert color columns to hex
- $row[$_] = sprintf("%06x", $row[$_]) foreach 1..2;
-
- my $newpack = pack($packfmt, @row);
- last if length($mempack) + length($newpack) > 950*1024;
-
- $mempack .= $newpack;
-
- # unless groupmask matches, skip adding to %friends
- next if $mask && ! ($row[3]+0 & $mask+0);
-
- # add "#" to beginning of colors
- $row[$_] = "\#$row[$_]" foreach 1..2;
-
- my $fid = $row[0];
- my $idx = 1;
- foreach my $col (@cols[1..$#cols]) {
- $friends{$fid}->{$col} = $row[$idx];
- $idx++;
- }
- }
-
- LJ::MemCache::add($memkey, $mempack);
-
- # finished with lock, release it
- $release_lock->();
-
- return \%friends;
-}
-
# <LJFUNC>
# name: LJ::get_friendofs
# des: Returns userids of friendofs for a given user.
@@ -8823,27 +8677,11 @@
my $userid = LJ::want_userid($uuid);
return undef unless $userid;
- # first, check memcache
- my $memkey = [$userid, "friendofs:$userid"];
-
- unless ($opts->{force}) {
- my $memfriendofs = LJ::MemCache::get($memkey);
- return @$memfriendofs if $memfriendofs;
- }
-
- # nothing from memcache, select all rows from the
- # database and insert those into memcache
-
- my $dbh = LJ::get_db_writer();
- my $limit = $opts->{force} ? '' : " LIMIT " . ($LJ::MAX_FRIENDOF_LOAD+1);
- my $friendofs = $dbh->selectcol_arrayref
- ("SELECT userid FROM friends WHERE friendid=?$limit",
- undef, $userid) || [];
- die $dbh->errstr if $dbh->err;
-
- LJ::MemCache::add($memkey, $friendofs);
-
- return @$friendofs;
+ my $u = LJ::load_userid($userid);
+ return LJ::RelationService->find_relation_sources($u,
+ nolimit => $opts->{force} || 0,
+ skip_memcached => $opts->{force},
+ );
}
# <LJFUNC>
