Tradeoff AnalyticsでAMIMOTOマネージドの最適プランを調べてみる
Tradeoff Analyticsについて Tradeoff Analyticsについては下の記事で紹介書いてるので、参考にしてください。 https://wp-kyoto.cdn.rabify.me/try-to-r […]
目次
Tradeoff Analyticsについて
Tradeoff Analyticsについては下の記事で紹介書いてるので、参考にしてください。
https://wp-kyoto.cdn.rabify.me/try-to-run-tradeoff-analytics/
どうせなら身近なものでテストしてみたいなと思ったので、ウチのサービスを題材にしてみました。
やってみたいこと
「価格・PV・WordPressインストール数とDBタイプの4種類から、希望条件に近いプランのリスト」を出すということを試してみます。
リクエストJSONを作る
最終的には1つのJSONにまとめますが、とりあえずパーツごとに作ります。
プロダクトリストを作る
まずは候補リストから。
候補はoptionsの中にリストとしてまとめていきます。
データソース
データはプラン一覧から拾ってきます。
https://ja.amimoto-ami.com/plans/hosting/single-instance-plan/
https://ja.amimoto-ami.com/plans/hosting/multi-instances-plan/
JSON
valuesの中に、価格・PV・DBタイプ・WordPressインストール数を入れてます。
"options": [
    {
        "key": "1",
        "name": "t2.micro",
        "values": {
            "price": 3000,
            "pv": 100000,
            "db": "EC2",
            "wp": 3
        }
    },{
        "key": "2",
        "name": "t2.small",
        "values": {
            "price": 6000,
            "pv": 300000,
            "db": "EC2",
            "wp": 3
        }
    },{
        "key": "3",
        "name": "t2.medium",
        "values": {
            "price": 15000,
            "pv": 500000,
            "db": "EC2",
            "wp": 3
        }
    },{
        "key": "4",
        "name": "c4.large",
        "values": {
            "price": 20000,
            "pv": 1000000,
            "db": "EC2",
            "wp": 5
        }
    },{
        "key": "5",
        "name": "c4.8large",
        "values": {
            "price": 350000,
            "pv": 20000000,
            "db": "EC2",
            "wp": 5
        }
    },{
        "key": "6",
        "name": "w-Small",
        "values": {
            "price": 80000,
            "pv": 3000000,
            "db": "RDS-Single",
            "wp": 5
        }
    },{
        "key": "7",
        "name": "w-Large",
        "values": {
            "price": 120000,
            "pv": 6000000,
            "db": "RDS-Single",
            "wp": 5
        }
    },{
        "key": "8",
        "name": "w-XLarge",
        "values": {
            "price": 160000,
            "pv": 10000000,
            "db": "RDS-Single",
            "wp": 10
        }
    },{
        "key": "9",
        "name": "w-2XLarge",
        "values": {
            "price": 320000,
            "pv": 20000000,
            "db": "RDS-Single",
            "wp": 10
        }
    },{
        "key": "10",
        "name": "HA-Small",
        "values": {
            "price": 120000,
            "pv": 3000000,
            "db": "RDS-Multi",
            "wp": 5
        }
    },{
        "key": "11",
        "name": "HA-Large",
        "values": {
            "price": 180000,
            "pv": 6000000,
            "db": "RDS-Multi",
            "wp": 5
        }
    },{
        "key": "12",
        "name": "HA-XLarge",
        "values": {
            "price": 200000,
            "pv": 10000000,
            "db": "RDS-Multi",
            "wp": 10
        }
    },{
        "key": "13",
        "name": "HA-2XLarge",
        "values": {
            "price": 460000,
            "pv": 20000000,
            "db": "RDS-Multi",
            "wp": 10
        }
    }
]
検索条件を入れる
columnsキーの中に検索候補を入れていきます。
"columns": [
        {
            "key": "price",
            "type": "numeric",
            "goal": "min",
            "is_objective": true,
            "full_name": "Price",
            "range": {
                "low": 0,
                "high": 300000
            },
            "format": "currency: 'JPY¥' : 2"
        },{
            "key": "pv",
            "type": "numeric",
            "goal": "max",
            "is_objective": true,
            "full_name": "Monthly Pageview",
            "range": {
                "low": 10000000,
                "high": 20000000
            },
            "format": "number:2"
        },{
          "key": "db",
          "type": "categorical",
          "goal": "min",
          "is_objective": true,
          "full_name": "Database Type",
          "range": [
            "EC2",
            "RDS-Single",
            "RDS-Multi"
          ],
          "preference": [
            "EC2",
            "RDS-Single",
            "RDS-Multi"
          ]
        },{
            "key": "wp",
            "type": "numeric",
            "goal": "max",
            "is_objective": true,
            "full_name": "WordPress installation ",
            "range": {
                "low": 1,
                "high": 10
            }
        }
    ]
検索条件を表にまとめるとこうなります。
| 項目名 | 範囲 | 
|---|---|
| 価格 | 0~300,000円 | 
| PV | 10,000,000~20,000,000 PV | 
| DBタイプ | EC2内DB > RDS1台 > RDSマルチAZ | 
| WPインストール数 | 1 ~ 10 | 
categoricalの分類方法
preferenceの配列順序でレコメンド度合いを取ります。
goalをmaxにした場合は配列の後ろの方にあるものを優先、minにした場合は前にあるものを優先します。
JSONにまとめる
最後にsubjectにタイトルを付けて、JSONにまとめます。
まとめたものが以下の通り。
{
    "subject": "amimoto",
    "columns": [
        {
            "key": "price",
            "type": "numeric",
            "goal": "min",
            "is_objective": true,
            "full_name": "Price",
            "range": {
                "low": 0,
                "high": 300000
            },
            "format": "currency: 'JPY¥' : 2"
        },{
            "key": "pv",
            "type": "numeric",
            "goal": "max",
            "is_objective": true,
            "full_name": "Monthly Pageview",
            "range": {
                "low": 10000000,
                "high": 20000000
            },
            "format": "number:2"
        },{
          "key": "db",
          "type": "categorical",
          "goal": "min",
          "is_objective": true,
          "full_name": "Database Type",
          "range": [
            "EC2",
            "RDS-Single",
            "RDS-Multi"
          ],
          "preference": [
            "EC2",
            "RDS-Single",
            "RDS-Multi"
          ]
        },{
            "key": "wp",
            "type": "numeric",
            "goal": "max",
            "is_objective": true,
            "full_name": "WordPress installation ",
            "range": {
                "low": 1,
                "high": 10
            }
        }
    ],
    "options": [
        {
            "key": "1",
            "name": "t2.micro",
            "values": {
                "price": 3000,
                "pv": 100000,
                "db": "EC2",
                "wp": 3
            }
        },{
            "key": "2",
            "name": "t2.small",
            "values": {
                "price": 6000,
                "pv": 300000,
                "db": "EC2",
                "wp": 3
            }
        },{
            "key": "3",
            "name": "t2.medium",
            "values": {
                "price": 15000,
                "pv": 500000,
                "db": "EC2",
                "wp": 3
            }
        },{
            "key": "4",
            "name": "c4.large",
            "values": {
                "price": 20000,
                "pv": 1000000,
                "db": "EC2",
                "wp": 5
            }
        },{
            "key": "5",
            "name": "c4.8large",
            "values": {
                "price": 350000,
                "pv": 20000000,
                "db": "EC2",
                "wp": 5
            }
        },{
            "key": "6",
            "name": "w-Small",
            "values": {
                "price": 80000,
                "pv": 3000000,
                "db": "RDS-Single",
                "wp": 5
            }
        },{
            "key": "7",
            "name": "w-Large",
            "values": {
                "price": 120000,
                "pv": 6000000,
                "db": "RDS-Single",
                "wp": 5
            }
        },{
            "key": "8",
            "name": "w-XLarge",
            "values": {
                "price": 160000,
                "pv": 10000000,
                "db": "RDS-Single",
                "wp": 10
            }
        },{
            "key": "9",
            "name": "w-2XLarge",
            "values": {
                "price": 320000,
                "pv": 20000000,
                "db": "RDS-Single",
                "wp": 10
            }
        },{
            "key": "10",
            "name": "HA-Small",
            "values": {
                "price": 120000,
                "pv": 3000000,
                "db": "RDS-Multi",
                "wp": 5
            }
        },{
            "key": "11",
            "name": "HA-Large",
            "values": {
                "price": 180000,
                "pv": 6000000,
                "db": "RDS-Multi",
                "wp": 5
            }
        },{
            "key": "12",
            "name": "HA-XLarge",
            "values": {
                "price": 200000,
                "pv": 10000000,
                "db": "RDS-Multi",
                "wp": 10
            }
        },{
            "key": "13",
            "name": "HA-2XLarge",
            "values": {
                "price": 460000,
                "pv": 20000000,
                "db": "RDS-Multi",
                "wp": 10
            }
        }
    ]
}
あとはこれをPOSTでwatson APIにぶん投げます。
Tradeoff Analytics APIで分析する
検索パラメータができたので、あとはAPIを使って解析します。
$ curl -X POST --user YOUR_USERNAME:YOUR_PASSWORD \ --header "Content-Type: application/json" \ --data @problem.json \ "https://gateway.watsonplatform.net/tradeoff-analytics/api/v1/dilemmas?generate_visualization=false"
戻り値を確認する
リクエストパラメータなども含まれた状態で返ってくるので、jqで見たい部分だけ抜き出します。
$ curl -X POST --user YOUR_USERNAME:YOUR_PASSWORD \
 --header "Content-Type: application/json" \
 --data @problem.json \
 "https://gateway.watsonplatform.net/tradeoff-analytics/api/v1/dilemmas?generate_visualization=false" \
 | jq ".resolution"
{
  "solutions": [
    {
      "solution_ref": "1",
      "status": "INCOMPLETE",
      "status_cause": {
        "message": "A column of a option is out of range. Option \"1\" has a value in column \"pv\" which is:\"100000\" while the column range\" is: [1.0E7,2.0E7]",
        "error_code": "RANGE_MISMATCH",
        "tokens": [
          "pv",
          "100000",
          "[1.0E7,2.0E7]"
        ]
      }
    },
    {
      "solution_ref": "2",
      "status": "INCOMPLETE",
      "status_cause": {
        "message": "A column of a option is out of range. Option \"2\" has a value in column \"pv\" which is:\"300000\" while the column range\" is: [1.0E7,2.0E7]",
        "error_code": "RANGE_MISMATCH",
        "tokens": [
          "pv",
          "300000",
          "[1.0E7,2.0E7]"
        ]
      }
    },
    {
      "solution_ref": "3",
      "status": "INCOMPLETE",
      "status_cause": {
        "message": "A column of a option is out of range. Option \"3\" has a value in column \"pv\" which is:\"500000\" while the column range\" is: [1.0E7,2.0E7]",
        "error_code": "RANGE_MISMATCH",
        "tokens": [
          "pv",
          "500000",
          "[1.0E7,2.0E7]"
        ]
      }
    },
    {
      "solution_ref": "4",
      "status": "INCOMPLETE",
      "status_cause": {
        "message": "A column of a option is out of range. Option \"4\" has a value in column \"pv\" which is:\"1000000\" while the column range\" is: [1.0E7,2.0E7]",
        "error_code": "RANGE_MISMATCH",
        "tokens": [
          "pv",
          "1000000",
          "[1.0E7,2.0E7]"
        ]
      }
    },
    {
      "solution_ref": "5",
      "status": "INCOMPLETE",
      "status_cause": {
        "message": "A column of a option is out of range. Option \"5\" has a value in column \"price\" which is:\"350000\" while the column range\" is: [0.0,300000.0]",
        "error_code": "RANGE_MISMATCH",
        "tokens": [
          "price",
          "350000",
          "[0.0,300000.0]"
        ]
      }
    },
    {
      "solution_ref": "6",
      "status": "INCOMPLETE",
      "status_cause": {
        "message": "A column of a option is out of range. Option \"6\" has a value in column \"pv\" which is:\"3000000\" while the column range\" is: [1.0E7,2.0E7]",
        "error_code": "RANGE_MISMATCH",
        "tokens": [
          "pv",
          "3000000",
          "[1.0E7,2.0E7]"
        ]
      }
    },
    {
      "solution_ref": "7",
      "status": "INCOMPLETE",
      "status_cause": {
        "message": "A column of a option is out of range. Option \"7\" has a value in column \"pv\" which is:\"6000000\" while the column range\" is: [1.0E7,2.0E7]",
        "error_code": "RANGE_MISMATCH",
        "tokens": [
          "pv",
          "6000000",
          "[1.0E7,2.0E7]"
        ]
      }
    },
    {
      "solution_ref": "8",
      "status": "FRONT"
    },
    {
      "solution_ref": "9",
      "status": "INCOMPLETE",
      "status_cause": {
        "message": "A column of a option is out of range. Option \"9\" has a value in column \"price\" which is:\"320000\" while the column range\" is: [0.0,300000.0]",
        "error_code": "RANGE_MISMATCH",
        "tokens": [
          "price",
          "320000",
          "[0.0,300000.0]"
        ]
      }
    },
    {
      "solution_ref": "10",
      "status": "INCOMPLETE",
      "status_cause": {
        "message": "A column of a option is out of range. Option \"10\" has a value in column \"pv\" which is:\"3000000\" while the column range\" is: [1.0E7,2.0E7]",
        "error_code": "RANGE_MISMATCH",
        "tokens": [
          "pv",
          "3000000",
          "[1.0E7,2.0E7]"
        ]
      }
    },
    {
      "solution_ref": "11",
      "status": "INCOMPLETE",
      "status_cause": {
        "message": "A column of a option is out of range. Option \"11\" has a value in column \"pv\" which is:\"6000000\" while the column range\" is: [1.0E7,2.0E7]",
        "error_code": "RANGE_MISMATCH",
        "tokens": [
          "pv",
          "6000000",
          "[1.0E7,2.0E7]"
        ]
      }
    },
    {
      "solution_ref": "12",
      "status": "EXCLUDED"
    },
    {
      "solution_ref": "13",
      "status": "INCOMPLETE",
      "status_cause": {
        "message": "A column of a option is out of range. Option \"13\" has a value in column \"price\" which is:\"460000\" while the column range\" is: [0.0,300000.0]",
        "error_code": "RANGE_MISMATCH",
        "tokens": [
          "price",
          "460000",
          "[0.0,300000.0]"
        ]
      }
    }
  ]
}
solutions.statusを確認していくと、solutions.solution_refが8のオブジェクトのみFRONTで、それ以外はINCOMPLETEやEXCLUDEDになっています。
INCOMPLETEはrangeの範囲外の値が設定されているもの、EXCLUDEDはTradeoff Analyticsが最適解で無いと判断したもので、FRONTとなっているものがTradeoff Analyticsからレコメンドされるアイテムです。
ということで今回はsolutions.solution_refが8のプランが希望する条件に一致する様子です。
solutions.solution_refはoptions.keyの値と同じなので、options.keyが8のアイテムを見てみましょう。
{
        "key": "8",
        "name": "w-XLarge",
        "values": {
          "price": 160000,
          "pv": 10000000,
          "db": "RDS-Single",
          "wp": 10
        }
      }
と、いうことで
| 項目名 | 範囲 | 
|---|---|
| 価格 | 0~300,000円 | 
| PV | 10,000,000~20,000,000 PV | 
| DBタイプ | EC2内DB > RDS1台 > RDSマルチAZ | 
| WPインストール数 | 1 ~ 10 | 
の条件に一致するプランは「w-XLarge」だということになりました。
"solution_ref": "12"がEXCLUDEDになっていますが、これはDBタイプが「RDS-Multi」と一番低い優先順位になっているためだと思われます。
{
    "key": "12",
    "name": "HA-XLarge",
    "values": {
        "price": 200000,
        "pv": 10000000,
        "db": "RDS-Multi",
        "wp": 10
    }
}
optionsのkey: dbのgoalをminではなくmaxに変更すると、このHA-XLargeプランもFRONTとなります。
まとめ
「複数の条件から最適なプランを探す」というのはシステム化するのが手間な印象がありましたが、Tradeoff Analyticsを使うことでJSONさえ作ればかなり簡単に作れることがわかりました。
あとはJSON生成の自動化やプランレコメンド調査用のUIを作るところのノウハウなどが集まるようになれば、もっと便利になるかなと思います。