Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/topn from db #44

Merged
merged 4 commits into from
Sep 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions isucon/portal/contest/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,7 @@ class TeamBenchmarkerDoesNotExistError(Exception):
class JobDoesNotExistError(Exception):
"""指定されたjob_idのジョブが見つからない際に発生する例外"""
pass

class TeamScoreDoesNotExistError(Exception):
"""チーム作成時、シグナルによって作成されるはずのScoreが存在しなかった際に発生する例外"""
pass
17 changes: 12 additions & 5 deletions isucon/portal/contest/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from isucon.portal.authentication.models import Team
from isucon.portal.contest.decorators import team_is_now_on_contest
from isucon.portal.contest.models import Server, Job, Score
from isucon.portal.contest.exceptions import TeamScoreDoesNotExistError

from isucon.portal.contest.forms import TeamForm, UserForm, ServerTargetForm, UserIconForm, ServerAddForm
from isucon.portal.redis.client import RedisClient
Expand Down Expand Up @@ -40,14 +41,20 @@ def dashboard(request):
recent_jobs = Job.objects.of_team(team=request.user.team).order_by("-created_at")[:10]
top_teams = Score.objects.passed().filter(team__participate_at=request.user.team.participate_at)[:30]

# キャッシュ済みグラフデータの取得
# topN チームID配列を用意
ranking = [row["team__id"] for row in
Score.objects.passed().filter(team__participate_at=request.user.team.participate_at).values("team__id")[:settings.RANKING_TOPN]]

# キャッシュ済みグラフデータの取得 (topNのみ表示するデータ)
client = RedisClient()
team_cnt = Team.objects.filter(participate_at=request.user.team.participate_at).count()
topn = min(settings.RANKING_TOPN, team_cnt)
graph_labels, graph_datasets = client.get_graph_data(request.user.team, topn=topn, is_last_spurt=context['is_last_spurt'])
graph_labels, graph_datasets = client.get_graph_data(request.user.team, ranking, is_last_spurt=context['is_last_spurt'])

# チームのスコアを取得
team_score = client.get_team_score(request.user.team)
try:
team = Score.objects.get(team=request.user.team)
team_score = team.latest_score
except:
raise TeamScoreDoesNotExistError

context.update({
"recent_jobs": recent_jobs,
Expand Down
34 changes: 5 additions & 29 deletions isucon/portal/redis/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import redis

from isucon.portal.authentication.models import Team
from isucon.portal.contest.models import Job
from isucon.portal.contest.models import Job, Score


jst = timezone('Asia/Tokyo')
Expand All @@ -17,17 +17,11 @@ class RedisClient:
LOCK = "lock"
# チーム情報(スコア履歴、ラベル一覧含む)
TEAM_DICT = "team-dict"
# ランキング
RANKING_ZRANK = "participate_at:{participate_at}:ranking"


def __init__(self):
self.conn = redis.StrictRedis(host=settings.REDIS_HOST)

@staticmethod
def _normalize_participate_at(participate_at):
return participate_at.strftime('%Y%m%d')

@staticmethod
def _normalize_finished_at(finished_at):
return finished_at.strftime('%Y-%m-%d %H:%M:%S')
Expand All @@ -53,9 +47,6 @@ def load_cache_from_db(self, use_lock=False):

team_dict['all_labels'].add(finished_at)

participate_at = self._normalize_participate_at(job.team.participate_at)
pipeline.zadd(self.RANKING_ZRANK.format(participate_at=participate_at), {job.team.id: job.score})

pipeline.set(self.TEAM_DICT, pickle.dumps(team_dict))
pipeline.execute()
finally:
Expand All @@ -74,7 +65,6 @@ def update_team_cache(self, job):
labels=[],
scores=[]
)
participate_at = self._normalize_participate_at(team.participate_at)
with self.conn.pipeline() as pipeline:
for job in Job.objects.filter(status=Job.DONE, team=team).order_by('finished_at'):
finished_at = self._normalize_finished_at(job.finished_at.astimezone(jst))
Expand All @@ -83,8 +73,6 @@ def update_team_cache(self, job):

all_labels.add(finished_at)

# ランキングの更新
pipeline.zadd(self.RANKING_ZRANK.format(participate_at=participate_at), {team.id: job.score})
pipeline.execute()

try:
Expand All @@ -107,36 +95,24 @@ def update_team_cache(self, job):
finally:
lock.release()

def get_team_score(self, team):
"""特定チームのスコアを取得します"""
target_team_id = team.id
target_team_participate_at = self._normalize_participate_at(team.participate_at)

ret = self.conn.zscore(self.RANKING_ZRANK.format(participate_at=target_team_participate_at), target_team_id)
if not ret:
return 0

return int(ret)

def get_graph_data(self, target_team, topn=30, is_last_spurt=False):
def get_graph_data(self, target_team, ranking, is_last_spurt=False):
"""Chart.js によるグラフデータをキャッシュから取得します"""
target_team_id, target_team_name = target_team.id, target_team.name
target_team_participate_at = self._normalize_participate_at(target_team.participate_at)

# pickleで保存してあるRedisキャッシュを取得
team_bytes = self.conn.get(self.TEAM_DICT)
if team_bytes is None:
return [], []
team_dict = pickle.loads(team_bytes)

# ラストスパートに入ったならば、自チームのみグラフに表示する
if is_last_spurt and target_team_id in team_dict:
return list(sorted(team_dict['all_labels'])), [dict(
label='{} ({})'.format(target_team_name, target_team_id),
data=zip(team_dict[target_team_id]['labels'], team_dict[target_team_id]['scores'])
)]

# topNランキング取得 (team_id の一覧を取得)
ranking = list(map(int, self.conn.zrange(self.RANKING_ZRANK.format(participate_at=target_team_participate_at), 0, topn-1, desc=True)))

# ラストスパート前は、topNのチームについてグラフ描画を行う
datasets = []
for team_id, team in team_dict.items():
if team_id not in ranking:
Expand Down