web-dev-qa-db-ja.com

(PHPで)データマイニングを効率的に実行する方法

私の会社には、データベースから収集されたデータに基づいて統計を提供するシステムに取り組む瞬間がやってきました。

ページをロードする際のオーバーヘッドやキャッシュ管理の複雑さを増やさないように、データベースから統計を効率的に収集するにはどうすればよいですか?

現在、統計は実行時に計算され、データは保存またはキャッシュされません。問題は、新しい統計を追加すると、それらも実行時に計算されるため、Webサイトの目的に到達することです。 be reaaally遅い、これは許容できません。

この問題を解決するために思いついた唯一のアイデアは、日付フィルターが計算された日を過ぎたデータをキャッシュすることです。

たとえば、ユーザーが2017-01-01から2017-01-08の間に特定のページにアクセスしたかどうかを知りたいとします。今日は2017-01-12であるため、選択された日付が古いため、この結果が将来変更されることはありません。

これは、Laravel(4.x)で統計を計算する方法の例です。

namespace App\Composers\Users;

use Illuminate\Support\Collection;
use User;

class ShowComposer
{
    public function compose($view)
    {
        $viewData = $view->getData();

        $view->with([
            'sellings'    => $this->getSellingStatistics($viewData['user'])
        ]);
    }

    public function getSellingStatistics(User $user)
    {
        $sellings = [];

        $getSellingsOf = function (User $user, $months) {
            $startOfMonth = \Carbon::now()->subMonths($months)->startOfMonth();
            $endOfMonth   = \Carbon::now()->subMonths($months)->endOfMonth();

             return $user
                ->mavs()
                ->whereHas('buyerProposal', function ($proposal) use ($startOfMonth, $endOfMonth) {
                    $proposal->whereBetween('sold_at', [
                        $startOfMonth, $endOfMonth
                    ]);
                })
                ->count();
        };

        $sellings['best'] = value(function () use ($getSellingsOf) {
            $months = [];

            for ($month = 0; $month < 12; $month++) {
                $startOfMonth = \Carbon::now()->subMonths($month)->startOfMonth();
                $endOfMonth   = \Carbon::now()->subMonths($month)->endOfMonth();

                $query = <<<SQL
            SELECT
                id, (SELECT COUNT(*)
                    FROM `mav`
                    INNER JOIN `mav_proposals` ON `mav`.`mav_proposal_id` = `mav_proposals`.`id`
                    WHERE sold_at BETWEEN ? AND ?
                    AND mav.user_id = users.id) AS sellings
            FROM users
            ORDER BY sellings DESC
            LIMIT 1
SQL;

                $response = \DB::select($query, [
                    $startOfMonth->toDateTimeString(),
                    $endOfMonth->toDateTimeString()
                ]);

                $user = User::find($response[0]->id);

                $months[] = $getSellingsOf($user, $month);
            }

            $months = array_reverse($months);

            return $months;
        });

        $sellings['personal'] = value(function () use ($user, $getSellingsOf) {
            $months = [];

            for ($month = 0; $month < 12; $month++) {
                $months[] = $getSellingsOf($user, $month);
            }

            $months = array_reverse($months);

            return $months;
        });

        $sellings['global'] = value(function () use ($user) {
            $months = [];

            for ($month = 0; $month < 12; $month++) {
                $startOfMonth = \Carbon::now()->subMonths($month)->startOfMonth();
                $endOfMonth   = \Carbon::now()->subMonths($month)->endOfMonth();

                $companySoldMavs = \App\Models\MAV::whereHas('buyerProposal',
                    function ($proposal) use ($startOfMonth, $endOfMonth) {
                        $proposal->whereBetween('sold_at', [
                            $startOfMonth, $endOfMonth
                        ]);
                    })->count();

                $usersWithSoldMavs = \User::whereHas('mavs', function ($mav) use ($startOfMonth, $endOfMonth) {
                    $mav->whereHas('buyerProposal', function ($proposal) use ($startOfMonth, $endOfMonth) {
                        $proposal->whereBetween('sold_at', [
                            $startOfMonth, $endOfMonth
                        ]);
                    });
                })->count();

                $months[] = ($usersWithSoldMavs > 0)
                    ? round($companySoldMavs / $usersWithSoldMavs)
                    : 0;
            }

            $months = array_reverse($months);

            return $months;
        });

        return $sellings;
    }
}

さて、ここで私が考えたのは2つのオプションだけです。

  • 24時間ごとに統計を計算し、データベースに保存します。
  • 統計の収集に使用されるパラメーターに基づいてデータをキャッシュします。

最初のオプションは非常に複雑で、適切に開発するには多くの時間がかかります。

2番目のオプションは興味深いかもしれませんが、キャッシュが遅かれ早かれ頭痛の種になると思います。

それを効率的に行う他の方法はありますか?企業はどのようにしてデータマイニングに移行しますか?これらの場合、Rなどの言語は常に使用されますか、またはPHPは適切に使用されれば問題ありませんか?

私にとって新しい世界です。親切にしてください。

3
GiamPy

これには2つの問題があります。

最初に結果を(データベースまたはキャッシュなどに)格納します。これにより、ページが読み込まれるたびに計算をやり直す必要がなくなります。

次に、すべての計算をやり直すためのメカニズムが必要です。古い計算に欠陥があるか、古いデータが更新/削除されているか、新しい計算が追加されている可能性があります。

これを行う最良の方法は、特定の時間まで(たとえば、昨日の真夜中)までの計算を含むテーブルをデータベースに追加することです。新しい計算が追加されると、それらのテーブルを更新するか、新しいテーブルを追加する必要があります。

必要なときはいつでも、テーブルを空にして、計算を行うスクリプトを実行できます。これは、毎晩自動的に実行されるスクリプトである可能性があります。

ただし、テーブルをクリアして手動でスクリプトを実行できる必要もあります。

重要な注意点の1つは、統計テーブルに永続的なデータが含まれているとは限らないことです。データは簡単に再生成する必要があるだけです。

古いデータは決して変更されないという考えは危険な仮定です。信頼できると見なされなくなったデータソースを使用できます。古いデータが古くなる理由はたくさんあります。変更されない古いデータに依存するシステムを設計することは、障害に対して設計することです。古いデータに依存する必要がないように設計する必要があります。

1
Bent