ポンコツエンジニアのごじゃっぺ開発日記。

いろいろポンコツだけど、気にするな。エンジニアの日々の開発などの記録を残していきます。 自動で収入を得られるサービスやシステムを作ることが目標!!

GASでお金をかけずにランキングを管理するAPIを作ってみよう

この記事は GAS道場 Advent Calendar 2019 の9日目の記事です。 Google Apps Script(GAS)をこれから使おうという方向けのアドベントカレンダーになります。

前回はJSONを返すAPIを作成しようってことを紹介しましたので、今回はそれを利用してランキングのAPIを実装してみようと思います。もちろんサーバ費は無料ですのでご安心を!

ソースコード

さっそくですが、以下のようなスクリプトを書いてみました。

function doGet(e) {
  var userId = e.parameter.user_id;
  var data = get(userId)
  var payload = JSON.stringify(data)
  var output = ContentService.createTextOutput();
  output.setMimeType(ContentService.MimeType.JSON);
  output.setContent(payload);
  
  return output;
}

function doPost(e) {
  var userId = e.parameter.user_id;
  var score = e.parameter.score;
  var data = set(userId, score);
  Logger.log(data)
  var payload = JSON.stringify(data)
  var output = ContentService.createTextOutput();
  output.setMimeType(ContentService.MimeType.JSON);
  output.setContent(payload);
  
  return output
}


function getSpreadsheet() {
  return SpreadsheetApp.openById('1xueDOTi5bxndfg1BOKCXCu_9UGiYUDWqvZwV5CqWdH1').getSheetByName('シート1')
}

function getRankingData() {
  return sort(getSpreadsheet().getDataRange().getValues());;
}

function get(userId) {
  var data = getRankingData();
  var index = search(data, userId);
  if (!index) return {};
  return {
    rank: index + 1,
    user_id: userId,
    score: data[index][1],
  };
}

function set(userId, score) {
  var data = getRankingData();
  var index = search(data, userId);
  var rank = null;
  if (index) {
    if (data[index][1] > score) 
      return {
        rank: index + 1,
        user_id: userId,
        score: data[index][1],
      };
    data[index][1] = score;
    data = sort(data)
    rank = search(data, userId) + 1;
  } else {
    data.push([userId,score]);
    data = sort(data).slice(0,1000)
    rank = search(data, userId) + 1;
  }
  setRakingData(data);
  return {
    rank: rank,
    user_id:userId,
    score: score,
  }
}

function search(data, userId) {
  for (var i = 0; i < data.length; i++) {
    if (data[i][0] == userId) return i;
  }
}

function sort(array) {
  array.sort(function(a,b){return(b[1] - a[1]);});
  return array
}

function setRakingData(data) {
  var rows = data.length;
  var cols = data[0].length;
  getSpreadsheet().getRange(1,1,rows,cols).setValues(data);
}

ちょっとわかりにくいかもですが、今回もデータベースとしてスプレッドシートを利用しています。 f:id:ponkotsu0605:20191208152349p:plain スプレッドシートのシートの中身はこのような感じで、1列目がユーザID、2列目がスコアになっています。

スプレッドシートにはソート済みのものが入っていて、レコード数がどんどん増えると処理時間も伸び続けてしまうため1000件の制限を入れて、トップ1000までを残すようにしています。

また、GETメソッドで指定のユーザのランキングを取得、POSTメソッドでランキングにスコアの登録ができるようになっています。

リクエストを投げてみよう

では、さっそくこのAPIに対してリクエストを投げてみましょう。

ランキング取得

まずは、user_id=1のユーザのランキングを取得してみます。

$ curl -L "https://script.google.com/macros/s/AKfycbx63R8B-AFcCz59ODZK05E__IEpuRdiNZVtHAJ_6rBJCp0IhH1/exec?user_id=1"
{"rank":44,"user_id":"1","score":84}

どうやらuser_idが1のものは44位なんですね。

ランキング登録

次に、user_id=1のユーザに新しいスコアを登録してみます。

$ curl -d 'user_id=1&score=200' -L "https://script.google.com/macros/s/AKfycbx63R8B-AFcCz59ODZK05E__IEpuRdiNZVtHAJ_6rBJCp0IhH1/exec"
{"rank":39,"user_id":"1","score":"200"}

新しいスコアの登録と、更新後のランキングがレスポンスで返ってきます。

もちろん、その後にGETで投げればランキングの取得ができます。

$ curl -L "https://script.google.com/macros/s/AKfycbx63R8B-AFcCz59ODZK05E__IEpuRdiNZVtHAJ_6rBJCp0IhH1/exec?user_id=1"
{"rank":39,"user_id":"1","score":200}

性能試験

では、今日もAPIのレスポンスタイムの計測をしてみたいと思います。 用意したデータセットは最大件数の1000件のユーザの情報をランキングにセットしています。

ランキング取得

今回はPHPでランダムなユーザをリクエストするスクリプトを書いてみました。

<?php

function get($userId) {
    $start = microtime(true);
    $url = 'https://script.google.com/macros/s/AKfycbx63R8B-AFcCz59ODZK05E__IEpuRdiNZVtHAJ_6rBJCp0IhH1/exec?user_id=' . $userId;

    $curl = curl_init($url);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_FOLLOWLOCATION,true);
    $response = curl_exec($curl);
    curl_close($curl);
    echo (microtime(true) - $start) . "\n";
}


for ($i = 0; $i < 20; $i++) {
    get(rand(1, 1000));
}

これを実行してみます。

$ php get.php
1.7813620567322
1.9114890098572
1.9322779178619
1.867527961731
2.8121409416199
1.792356967926
2.01282787323
1.8207070827484
1.8467609882355
1.7276220321655
1.9059901237488
1.7851519584656
1.9352650642395
2.1070241928101
1.7733509540558
2.2467129230499
1.8767580986023
1.9473218917847
1.7479920387268
2.3125109672546

ということで、平均が1.96秒と、2秒弱もかかってしまっています。ちなみに、上のデータは取得するユーザが必ず存在するパターンでテストを行いましたが、以下はヒットしなかった場合の時間計測をしています。

2.380439997
2.01256609
1.913767815
2.324702978
1.896620035
1.968999863
1.764637947
2.448060036
1.90319705
2.267748117
2.445827007
2.048094988
1.972090006
2.12915802
2.004635096
2.171488047
1.970641136
1.886492014
2.302488089
1.942471981

こちらは、平均が2.09秒と若干増えてるように感じます。

ランキング登録

次はランキングの登録です。 使用するスクリプトは以下になります。

<?php

function post($userId, $score) {
    $start = microtime(true);
    $url = 'https://script.google.com/macros/s/AKfycbx63R8B-AFcCz59ODZK05E__IEpuRdiNZVtHAJ_6rBJCp0IhH1/exec';
    $params = [
            'user_id' => $userId,
            'score' => $score,
    ];

    $curl = curl_init($url);
    curl_setopt($curl, CURLOPT_POST, TRUE);
    curl_setopt($curl, CURLOPT_POSTFIELDS, $params); // パラメータをセット
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_FOLLOWLOCATION,true);
    $response = curl_exec($curl);
    curl_close($curl);
    echo (microtime(true) - $start) . "\n";
}


for ($i = 0; $i < 20; $i++) {
    post(rand(1, 1000), rand(1, 1000));
}

これを投げてみます。

$ php post.php
2.3142991065979
2.0768530368805
2.2164149284363
2.1746621131897
2.3409171104431
2.881902217865
2.2780590057373
2.4994249343872
2.7755930423737
1.9212970733643
2.4395508766174
2.4899280071259
2.1394140720367
2.1406629085541
2.255252122879
2.7269558906555
2.2563080787659
2.1866700649261
2.1948499679565
2.0703001022339

ランキング更新については、平均が2.32秒かかっていることがわかります。また、こちらに関しても、すでに登録済みの更新APIですが、次は新規ユーザとしての新しくスコアを登録するものをやってみます。

3.290426016
2.700387001
2.470891953
2.762756109
2.790122986
2.636446953
2.113222122
2.05518198
2.163753033
2.549683094
2.64330101
2.060091019
2.167116165
2.093100071
1.958088875
2.017930984
2.133880854
2.247919083
2.335860968
2.052029133

こちらは平均が2.36秒とちょっとだけ増えた感じはありますが、あまり変わりませんね。

さいごに

このように、ランキングを管理するAPIを作成してみました。レスポンスが2秒とかかかってしまうので、ゲームとか実際のサービスには向いてないかもですが、今回はランキングをリアルタイムで更新して、常に最新のランキングを返すような作りになっていたので、レスポンスに時間がかかってしまった可能性があります。APIを叩くときは単純にデータの挿入だけしておいて、別途非同期にランキングを更新するスクリプトを実行することで、レスポンスを改善するような工夫をしてもいいかもですね。 また、今回はトランザクションを気にせず作ってしまいましたので、もしかしたら同時にリクエストが来た場合にデータがおかしくなる可能性がありますのでご注意ください。