Pull to refresh

Пять способов ускорить запросы API Facebook на практике

Facebook API *
Ни для кого не секрет, что самым узким местом веб-приложений чаще всего являются HTTP-запросы к внешним серверам. Так, время загрузки данных запроса API много больше чем время, необходимое для выполнения большинства самых сложных скриптов веб-приложения.

За время работы с API Facebook я накопил несколько рецептов оптимизации запросов: как увеличить скорость работы скриптов, уменьшить их количество и ресурсоёмкость.



Способы, изложенные в этой статье, работают только с API Facebook. Но я не исключаю, что они могут быть применимы и в других сервисах, предоставляющих API.

Введение


Наверное, у вас есть идея веб-приложения, которое работает с данными пользователя и его социального графа Facebook. И вы уже не раз открывали документацию и даже успели написать пару скриптов. Пусть это будет приложение “подарок на день рождения”, и первое что вы должны сделать — получить список друзей пользователя и их даты рождения.

Все просто, думаете вы:
  1. авторизую пользователя (получаю id пользователя и access_token, которые использую в дальнейших запросах);
  2. получаю список его друзей (на выходе имею массив с id и name друзей);
  3. для каждого id делаю запрос данных пользователя (чтобы получить данные birthday, при авторизации нужно запросить friends_birthday permission).

Так как ваш скрипт работает на стороне сервера, вы должны получить все данные до их вывода пользователю. И тут вы сталкиваетесь с первой и самой важной проблемой: один запрос к API занимает 1-2 секунды. Так, чтобы только получить список всех друзей вам понадобится 2-4 секунды (в зависимости от количества друзей).

Попробуем посчитать, сколько времени будет выполняться скрипт который выводит дни рождения 10-и друзей.
<?php
$start = microtime(true);

$app_id = "YOUR_APP_ID";
$app_secret = "YOUR_APP_SECRET";
$my_url = "YOUR_URL";

session_start();
$code = $_REQUEST["code"];

if(empty($code)) {
  $_SESSION['state'] = md5(uniqid(rand(), TRUE)); //CSRF protection
  $dialog_url = 'https://www.facebook.com/dialog/oauth?client_id='
    . $app_id . '&redirect_uri=' . urlencode($my_url) . '&scope=user_birthday,friends_birthday&state=' . $_SESSION['state'];
  echo("<script> top.location.href='" . $dialog_url . "'</script>");
}

if($_REQUEST['state'] == $_SESSION['state']) {
  $token_url = 'https://graph.facebook.com/oauth/access_token?'
    . 'client_id=' . $app_id . '&redirect_uri=' . urlencode($my_url)
    . '&client_secret=' . $app_secret . '&code=' . $code;
  $response = file_get_contents($token_url);
  $params = null;
  parse_str($response, $params);  

  $graph_url = "https://graph.facebook.com/me?access_token=" . $params['access_token'];
  $user = json_decode(file_get_contents($graph_url));
  $uid = $user->id;

  // получаем список друзей пользователя
  $graph_url = 'https://graph.facebook.com/'.$uid.'/friends?limit=10&access_token=' . $params['access_token'];
  $friends = json_decode(file_get_contents($graph_url)); 

  $time = microtime(true) - $start;
  printf('Авторизация и получение списка друзей %.4F сек.<br/>', $time);

  $n = sizeof($friends->data);
  for ($i = 0; $i < $n; $i++) {
    $graph_url = 'https://graph.facebook.com/' . $friends->data[$i]->id . '?access_token=' . $params['access_token'];
    $friend_data = file_get_contents($graph_url);
    // декодировать json ответ и вывести данные
    //$friend = json_decode($friend_data);
    //echo($i.' '.$friend->name.' - '.$friend->birthday);
  }
}
else {
  echo("The state does not match. You may be a victim of CSRF.");
}

$time = microtime(true) - $start;
printf('Скрипт выполнялся %.4F сек.', $time);

Мне понадобилось 13 секунд для этого! Будет ли ждать ваш пользователь столько времени? Не думаю. А если количество друзей измеряется сотнями, или даже пару тысяч…

Давайте начнем оптимизацию.

Способ 1: Читаем только необходимые поля


Когда вы запрашиваете данные пользователя (объект User в Graph API), Facebook по умолчанию передает вам все поля, к которым у вас есть доступ. Вы можете избавиться от избыточной информации, указав какие поля вам нужно вернуть. Для этого в запросе используется параметр fields, в котором необходимые поля перечисляются через запятую. Например, для нашего случая это будет запрос: {user_id}?fields=id,name,birthday
  1. Разрешите необходимые permissions в Graph API Explorer, нажав Get Access Token и установив все флажки в User Data Permissions и Friends Data Permissions.
  2. Введите запрос me в поле, после https:graph.facebook.com/
  3. Сравните предыдущий вывод с запросом me?fields=id,name,birthday



Запросы с параметром fields работают быстрее, так как возвращают только нужные данные. Они быстрее передаются по сети, так как имеют меньший размер. Обрабатываются они тоже быстрее, и используют меньше памяти.

Хотя на общее время выполнения этот способ оптимизации в данной программе практически не влияет, но именно его никогда не стоит забывать. Читайте только необходимые поля для каждого запроса!

Способ 2: Запрашиваем данные нескольких объектов в одном запросе


Используя параметр ids вы можете выбрать несколько объектов, которые хотите получить. При этом вы можете использовать и параметр fields чтобы ограничить только нужные поля объектов (объекты должны быть одного типа). Например, запрос ?ids=4,501012028 позволит получить открытые данные сразу двух пользователей ;)

Количество объектов, которые вы можете указать через запятую, ограничено только максимальной длинной URL. При этом не забывайте, что размер ответа на запрос тоже возрастает. Поэтому разумно ограничивайте количество объектов в одном запросе. Например, вот так выглядит ответ, если использовать первый и второй способ.


Чтобы использовать этот способ в нашем примере, нужно переписать скрипт.
$n = sizeof($friends->data);
$graph_url = 'https://graph.facebook.com/?ids=';

for ($i = 0; $i < $n; $i++) {
  $graph_url .= $friends->data[$i]->id . ',';
}

$graph_url = substr($graph_url, 0, -1);
$graph_url .= '&access_token=' . $params['access_token'];
$friend_data = file_get_contents($graph_url);
// декодировать json ответ и вывести данные

Теперь вместо 10 запросов мы используем всего один, соответственно время работы скрипта уменьшилось до 4-5 секунд. Но нам все еще нужно получить данные всех друзей, а не только 10-и.

Способ 3: Используем фильтрацию и пагинацию


Пагинация — это всего лишь разбиение информации на “страницы”. С помощью параметра limit мы получали информацию только о 10 друзьях. Чтобы получить такими же порциями информацию об остальных, существует параметр offset. Например, первая десятка друзей: me/friends?limit=10&offset=0, вторая десятка друзей: me/friends?limit=10&offset=10, и т.д.

Вы можете оптимизировать работу скриптов следующим образом:
  1. Пишите клиентскую часть с Ajax запросами к серверу.
  2. Получаете данные первых 10-и друзей и выводите их.
  3. Пока данные выводятся на экран, подгружаете следующую “порцию” данных.

Теперь для вывода информации вам не нужно ждать пока обработаются все части запросов. При этом для первой порции у вас уже будет получено одним запросом достаточный объем данных.

Перейдем к более серьезным способам, позволяющим за один запрос получить в десятки раз больше данных (а может и все одним разом).

Способ 4: Строим запросы FQL (Facebook Query Language)


FQL дает вам возможность использовать SQL-style интерфейс для запросов к данным. С его помощью можно проще реализовать некоторые запросы, не доступные через Graph API. Форма запроса следующая: SELECT [fields] FROM [table] WHERE [conditions]. Но FQL имеет много ограничений (если сравнить его с SQL). Так, например, в FROM можно использовать только одну таблицу. Но вы можете использовать вложенные запросы. В FQL можно использовать логические операторы, конструкции ORDER BY и LIMIT и некоторые другие операторы.

Чтобы получить имя и дату рождения пользователя, нужно сделать FQL запрос к таблице user. Например, в Graph API Explorer это будет запрос fql?q=SELECT uid, name, birthday_date FROM user WHERE uid = me(). А для того, чтобы получить список id своих друзей, используем запрос к таблице friend fql?q=SELECT uid2 FROM friend WHERE uid1 = me().

Давайте попробуем объединить два запроса в один, используя вложенный запрос. Все просто: fql?q=SELECT uid, name, birthday_date FROM user WHERE uid IN (SELECT uid2 FROM friend WHERE uid1 = me()). Вместо десятков запросов мы получили всю необходимую нам информацию всего за один!


// получаем список друзей пользователя и их дни рождения
$graph_url = 'https://graph.facebook.com/fql?q=SELECT uid, name, birthday_date FROM user WHERE uid IN (SELECT uid2 FROM friend WHERE uid1 = '.$uid.')&access_token=' . $params['access_token'];
$frnds = file_get_contents($graph_url);
// декодировать json ответ и вывести данные

Способов оптимизации запросов FQL довольно много. Изучите все таблицы в документации. Но FQL это не вершина оптимизации запросов.

Представьте, что вам необходимо получить список последних постов пользователя, его ленту новостей, все непрочитанные уведомления и новые сообщения. И все это, и многое другое, нужно сделать быстро. Можно, конечно, распараллелить запросы, сделав их ассинхронными. Но это не наш способ.

Способ 5: Batch Requests


С помощью Graph API вы можете обрабатывать данные всего одного запроса, даже если это FQL-запрос. Batch Request позволяет отправить на сервер в одном запросе — пачку из нескольких разных запросов. Единственное ограничение — до 20-и запросов в одном вызове batch. Запросы могут быть GET, POST и DELETE.

Например, вот такой “пакет” запросов:
  • Отправить новый статус с ссылкой.
  • Получить информацию о последнем статусе (новый не учитывается).
  • Лайкнуть последний статус и написать комментарий.

$batched_request = '[{"method":"POST","relative_url":"me/feed","body":"message=Скоро новый пост&link=http://habrahabr.ru/"}';
$batched_request .= ',{"method":"GET","name":"get-post","relative_url":"me/feed?limit=1"}';
$batched_request .= ',{"method":"POST","relative_url":"{result=get-post:$.data.0.id}/likes"}';
$batched_request .= ',{"method":"POST","relative_url":"{result=get-post:$.data.0.id}/comments","body":"message=Новый автоматический коммент"}';

$batched_request .= ']';

$url = 'https://graph.facebook.com/';
$param = array();
$param['access_token'] = $params['access_token'];
$param['batch'] = $batched_request;

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $param);
$ret = curl_exec($ch);

// обработка ответа
if($ret == 'false') echo '=false=';
curl_close($ch);

Batch запросы должны отправляться методом POST, поэтому в коде выше задействован cURL. Также с его помощью проще работать с возможными ошибками.

Все способы применения Batch Requests не описать в данной статье. Вы можете узнать о них больше в документации.
Tags:
Hubs:
Total votes 84: ↑81 and ↓3 +78
Views 13K
Comments Comments 27