WAS (Web Application Server)의 가장 큰 역할은 DB에서 데이터를 효율적으로 가져와서 Client에게 전달하고, Client에게 받은 데이터를 저장하는 것이라고 생각합니다.
개인적으로 농구에 관심이 많아서 아마추어 선수들도 프로선수들 처럼 자신의 대회기록을 제공하는 사이트를 사이드 프로젝트로 진행하고 있습니다. 아직 개발 중이라서 홍보는 하고 있지 않습니다.
아무래도 기록과 관련된 서비스이다보니 Query를 많이 사용하는 서비스입니다.
Team 의 상세페이지를 구현하면서 query 응답속도를 줄인 경험을 공유하려고 합니다.
아직 사용자가 많지 않은 서비스이다 보니 많은 시간을 할애 하지는 않았습니다. 그래서 혹시 부족한 부분이 있고, 조언해주실 부분이 있으면 정말 환영합니다.(사실 이 부분이 포스팅하는 이유 중에 가장 큽니다..ㅎㅎ)
그럼 이제 먼저 어떤 페이지인지 부터 소개드리면 아래와 같은 페이지입니다.
수정 전
SQL Time -> 11queries, 9.62 ms
위와 같은 페이지이고 실제 Django debug toolbar 라이브러리를 이용해서 살펴보면 다음과 같은 SQL 들이 실행됩니다.
실제 요청하는 SQL 리스트
어떤 코드가 위와 같은 Sql 을 실행시켰는지 살펴보면 아래와 같습니다.
수정 전 코드
views.py 코드
def team_detail(request, pk):
# team 상세
team = get_object_or_404(Team, id=pk)
return render(request, 'team/team_detail.html', {
'team': team,
'team_home_score': team.home_average_score(), //팀의 홈 평균 득점
'team_away_score': team.away_average_score(), // 팀의 어웨이 평균 득점
'home_match': team.home_match_qs, // 홈 최근 3경기 기록
'away_match': team.away_match_qs, // 어웨이 최근 3경기 기록
'record': team.match_count(), // 경기결과 총 경기수 / 승수 /패배 수
'join_seasons': team.join_season()
})
이번에 query 최적화 작업에서 Raw SQL 은 최대한 사용하지 않을 것 입니다.
실제 SQL을 실행시키는 orm query code
홈 경기평균 득점
home_average_score = Match.objects.filter(team1_id=self.id).\
aggregate(team1_score=Avg('team1_score'))['team1_score']
어웨이 경기 평균 득점
away_average_score = Match.objects.filter(team2_id=self.id).\
aggregate(team2_score=Avg('team2_score'))['team2_score']
홈 최근 3경기
home_match_qs = self.team1_match.select_related('team1').\
select_related('team2').all()[0:3]
어웨이 최근 3경기
away_match_qs = self.team2_match.select_related('team1').select_related('team2').\
all()[0:3]
팀 경기수
전체 경기수
total_match_count = Match.objects.filter(Q(team1_id=self.id) | Q(team2_id=self.id)).count()
승리 경기수
win_match_count = Match.objects.filter(winner_id=self.id).count()
패배 경기수
lose_match_count = total_match_count - win_match_count
record = {
"win_rate": (win_match_count / total_match_count) * 100,
"win": win_match_count,
"lose": lose_match_count
}
참여한 대회 목록
join_season = self.jointeam_set.all()
이제 어떤 페이지이고 어떤 SQL들이 실행되는지 살펴봤으니 어떻게 응답시간을 줄이건지 살펴보겠습니다.
이번에 ORM Query 최적화해서 집중할 부분은 다음과 같은 부분이었습니다.
1. 실제 실행되는 Sql 수를 줄이자. (통신 비용이 가장 큼)
2. 실제 필요한 데이터만 가져오자. (필요없는 것들 까지 가져오지 말자)
3. 가독성은 포기하지 말자.
4. 너무 많은 시간을 할애하지는 말자. (아직 많은 분들이 이용하는 서비스는 아니어서ㅠ)
1. 필요한 필드만 가져와보자 ( only, values, values_list 메서드 이용)
-기존 query 코드
홈 최근 3경기
장고 Query
home_match_qs = self.team1_match.select_related('team1').\
select_related('team2').all()[0:3]
실제 실행되는 sql
어웨이 최근 3경기
장고 Query
away_match_qs = self.team2_match.select_related('team1').select_related('team2').\
all()[0:3]
실제 실행되는 sql
-변경한 query 코드
홈 최근 3경기
장고 query
def home_match_qs(self):
home_match_qs = \
self.team1_match.select_related('team1', 'team2').\
only("team1__name", "team2__name",
"team1_score", "team2_score", "date")[0:3]
return home_match_qs
실제 실행되는 sql
l![](data:image/png;base64,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BAKBQCAQCAQCgUAg0B0CiLU333wzzT777E1Jrn//+9/pBz/4Qbr66qtz5D5suc8++6SpppqqNrFXX301XXTRRWm99dZLE0wwQW0YRN5LL72Uib055vgfl1QbeDy82JJc62t5t9lmm/Sf//wng97qePrpp7dN4u9//3t677330uSTT54mmWSStuH7E4CCvfHGGznflKqZ0vQnjXg2EAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoHBQIDHGu8x0sxz7brrrks333xzOvbYY9MHPvCBdMcdd6T3ve99TcPjdQjOpJVMN910I5ZcG5Q917gMqsR2x1aV8vbbb6dNN900vf/970/TTjttPp5//vn5EdcRX9Xfdtttl++ttNJKae211x4j6hNOOGG08J5deOGFMwkoMI87zK60pplmmjTXXHOlG264YYxnSprPPPPMGGnEhUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEBieCHDuQTjNOeecmSSqluL1119PO+20U+YKpp9++rT55punt956qxokn2+22WbpyCOPHOP62LqAi0GGOTb7Pffcc2mhhRbKHAhHJjwKZybhb7rpprTvvvum3XbbLV144YX5WiHVmsVXrpd0x1ZZh1I6g+K5BthWHmsIqsJ8NgPjggsuSD/+8Y/TnnvumdZff/105513JgpMxI+JPe644zL55doss8zikAm9fNLkz8knn5zXAbs90UQT5efPPPPMhJxbfPHFk/tTTz11uuaaa7KynXfeeTnc17/+9TTzzDOnvffeO73zzjuZgGuSRFwOBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBYYbAPffck0k1fEUjZ+H/ySabLHt8vfvuu3mJ5OGHH54OPvjg3lJyCMIhHHTQQb3XxsUJMkx+C/fSeFx66aXTOeeckyaeeOK0+uqrZ8cjYR5//PF0xBFH5CWiM800Uy4bb7RlllkmFwMXQxrjq/6fA4zAP4NCrvFYK8IDrSqUsRPBChN7ss0///xpxRVXHO0xHmYbbbRRL7k22s0W/6y77rppttlmGy3EqaeemqaYYorENbIQeKuttloO88UvfjEfjz766EzKSTMkEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgExi8Ell122eR3/fXXj1EwK9yOOuqo3utbbrlleuCBB3r/5w221157pQ022KD32rg6KSRY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WN3RNXLrrbdmLzcfI1h00UVzeMfvf//7+XfSSSeljTfeOHu12UttiSWWSLvttlu699570zHHHJNmmWWWNMkkk+S42qU5vu61lgvf8+d95SSOgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAsMbAR5iyK5WRyVEmP3qV79K11xzTXrnnXdy+E996lN5X7Stt946LxldffXVe/dmW2mllRJvt0984hPZsw3JVrzgWqVV8jK8UW2d+yFNrt1yyy3ZBbF1Ef53969//Wuymd5wEBv53XXXXbVZ5Xp51VVX1d4rFzWA3/3ud+Xftsf77rsvPfnkkzncE088kS666KJe9812D3eSn2oczz//fNOyVcMNhfNW9dAJTt3q6FAoszxU9aHb+u22zNEuh0qtt89Hf/SiWz0aX/RifO4n2mtM9yF+/vOfp7/85S8dPWhG9Uc/+lF66qmnOgov0M0335x8sWo4iJnil19+uTar7XDqCza1CY1HFxkEp59+enr11VfHo1JFUQKBQCAQCAQCgb4hgOhq9SuE2M4775w/bLDPPvsk3mueMZZaZZVVMtm23HLLpbfffjtfl5P33nsvX//73/+epp9++vzhhOIF1yq9cq9vpRkeTw1pcs3XLbg1M8I6kV//+tfpl7/8ZSdBBzXMW2+9ld59992WaVx77bXp8ssvrw0z55xzZhfM3//+97X3XfRZ3W9961ujDSIRi9/+9rdrn/nBD36Q/vCHP+R7XDcvueSSdMUVV9SGbbzYSX6qzyANm5WtGm6wz3UKOoJW0qoeOsGpmY76BHH1yyut8tDXe8gMLrkHH3xwbRSd6kO39duszLWZ6Lk40tslXMaGPsB5q622Sssuu2zaaKONMu51dTJYetGtHo0vejHc+4k6HRnMazbK3W+//fLArF06lg7oww844ICOJ4OOPfbY9Nprr7WLelDvG2B2koejjjoqIWfrpB1OzbAxcH399dfrohywawbV9mL5zGc+k1ZcccX0ta99Ldk4eVzLpJNOmr9AdvTRR4/rrET6gUAgEAgEAoHAOEegkFnNjoVcw12YmPLzjhf+lVdeSeeee25aY401slOOr3y6ToxzSpwPPPBAXhZa4irXWx3HOTCDmIEhTa7NPffc6fDDD+/1uGqHw1BZw4vg+sUvftEyu63y6isaRxxxRMvB+TLLLJOwy48++mhvOjPPPHNaf/31e/+vnkw44YS9/xqAfu9732tLPJUHOslPCevIKBgKYp34D3/4w5ZZaVUPneDUTEc333zzvPljy8T7efOGG25I8lhmChqj61Qfuq3fZmVuTL/83wrjEmZsHMdVu1S2saEPjPlNNtkku3Wvs8466Zvf/GaeSWrEdrD0ols9Gl/0Yrj3E436Mdj/+0T7Zptt1vF7/Stf+Uqy/KCZh1djfofC+8fg9JOf/GTbSbZWee0EpzpsHn744fT5z3++6XuhEa++/K/tmrxCsF166aWZzLPnylCQvfbaKy9jaTexNhTyGnkIBAKBQCAQCAQGE4FWBFe5J31ON+edd17+nX/++dkrjWMOvmH33XdPiyyySDrttNN6yTUr4C6++OK835rJNZ5txR4t8bY6DmaZx3XcQ/6DBr5SUZVnn302nXzyydllEbHEUPTlisbPuR5//PGZ3DAIXG211bLRefbZZydeN9YMk5/97GfZu2jDDTesJpGQFn68z8wqL7XUUmmKKabInl8265PeQgstlJXK8kxM7Yc//OG04447Jks5LK2iaNYfm6G39PCUU07JxsQMM8yQlVSCDz74YDrooIOSr23wfth7773TNNNMk/PyoQ99KPm1klKOEubNN9/MeZRfXlMaAW81cWKVV1hhhRI0zTXXXPnXe6HNSWN+uIEiPieffPJcDp50vGYWW2yx0WLS8Oowkr+C+29/+9v0yCOPpC233HK0Z3lmXXbZZbmu3R81alSab775kmWJGqzwGjNvRZ5yBtPzzjtv+vrXv55uuummxPNv4oknTs8880yuB6z8iSeemNOaaqqpcn4l2KoeOsGpUUfpmfo/88wzM/7bbbddruNTTz01f3p4/vnnzx5nU045ZarT007LzYUXtvCok270obF+6+KrXmssc3/04ZxzzumoXXaKS50+XH311WO0SzMy3epDJzg1tss6fbC3gXIjDBZffPG0yy67JAT4YYcdlglz/cx6662X7HfQablLe1JPm266aV4eRQfoWVUGUy8a8RkpejGc+4nnnnsukyR/+9vf8nvIpI3+0UBLW9LXeofSKX3pCSeckDzjnbXTTjvltmsyyfsOwctzku4anOnbEUhI349//OO9athsEqg3QOXEO3yLLbaoXEnpxhtvzD9tWF+74IIL5j51sskm6w2H/NGevH/f//735zzYgPfQQw/N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CdJkk03We21snLz22mtpqqmmSu973+DxyZ1gVFe3MN1qq60yeTnRRKM3yWoddhJ/Fctm4REt//rXv3LdVMMP9vlQroPBLntd/N30b3vuuecYxBi9OeKII/IExE9/+tP07LPPpgUXXDDtuuuu6YMf/OBoSU466aQJoX/00Uengw46qPfexRdfnNvjRhtt1HvNySuvvJL7lxtuuCF5tpP+xnP0ascdd+wl5VwjyLoTTjghnXnmmUnfh+T70Ic+lPbee+8022yz/X+g//5tprejBRoL/zTrw8ZC0pFEIBAIBAKBQCAQCAQCgUAgEAgMMwQGj2kYACDmnnvudPjhh2cSqpPokDZDQb797W8npEgraZVXxiWjGRnRTJZZZpm0zz77pEcffbRZkDThhBM2vVdudJJXYeVXXSB/ijdLiaPuyCD/3ve+l95+++3Rbn//+99PP/zhD0e7Ntj/MPjXXnvtxAtsMKUTjOrqVj1tu+22tcRftQ47ib9avmbhf/SjH+W6qYYd7POhXgeDXf66+Lvp377+9a+nfffdN6200kqZ4P7Wt76VtF36REf233//dNlll6WZZpopfec736lLLpNlc8wxx2htEnm/9NJLjxG+kYTupL8RibzIl7wi+kwQyOs3v/nNTNKZdDj22GOzVyui/bjjjhsj7WZ6O0bAQb7QrA8b5GQj+kAgEAgEAoFAIBAIBAKBQCAQGIYIjO4mMwQL8NGPfnS0XPHOOPnkkzPJg1iaeeaZ01e/+tXEaKzK8ccfnyz/YqitttpqaZNNNklnn3124i2y+uqr56A/+9nPsqG64YYbVh9NvDX83n333WRp0FJLLZW9fP7whz9kbzTpLbTQQonXh+VSyKYPf/jD2WPj5z//ebrvvvvSiy++mO6999603377ZY+RU045JZOEM8wwQ9p9991zeg8++GD2InnssccSLwleHNNMM02+x6vDr5WUclTDIPXkgafIG2+8kRZZZJF8+5e//GW6/PLLs2E977zzJsb61VdfPUZe5RsBptyw2m233dKMM86Y42DI87DqVHgbVj0OLfH9/e9/n736nnnmmYzNe++9l43thx9+OBMFO++8c5K/uvwKizhgsD/wwAO5TlZcccXsUffSSy+ltdZaK33uc58bI3tIPs86IhM23njjdN1112XPur///e9plVVWSVtssUX2ejzssMNyfb3/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W9mHrZBr5Thek2tvvvlmeu211/IXHhqB6sv/F110Ubr77rs7fvT3v/993gSv4wfGYcA//elP6YknnqjNwU9/+tP0m9/8pvbecL74j3/8I7366qvp3XffHdBidKsnA5r4OIqsGx0ZyHb5hz/8IZ133nm51N3kwQPce5vp/DiCsWmyDz74YLrvvvtq719zzTV5NqX25n8vDledvO6663pdsLut32Z4+LT2d77znTzz1CzM+HbdAOAHP/hB+utf/9q0aC+99FL67ne/mz893jRQ5cY777yTfvWrX+UrdfHbjwLO+tjxSYwp3njjjTxrOVDl6hb7gUp3XMYzEss8LvGOtAOBQCAQCAQCgUCgbwhYJjr99NOnT3ziE2nLLbdM88wzT/6wQSHUylHsQ5Zc43LH1a7x98wzz6Rdd9219/rEE0+cPvKRj6Sf/OQnvWj97W9/SxtssEGaaqqp8npZn1I96KCDegfD5flVVlml9xknX/3qV3O8a6+99mjXyz82sNtss806JmOOOuqo9Nhjj5XHx9mRccPQaSWMIK6OdTLTTDOlL33pS02NUbjMPvvsqeB2wgknZBy32Wab3uiuuuqqfE39lbqdbbbZeuMUx8wzz5wOPvjg/Iw1zo11X3dPHa+11loJ+dBMfOlDXBdeeGEOwjDaYostsqsnN89JJ5007bjjjr1G5a9//evRwpe83HDDDb1JYK9LeXsv/vekTk80unFhZDKA33rrrcYsDvj/7XREgu3aZTu9acz0yy+/nDbaaKP0oQ99KN/qJA/VOE499dSmOl8NN9jnr7/+etsJANhccskltVlZcMEF01e+8pWWfU2dToqsk76hNtEuLvrazqc+9al06KGH1j511llnJb9GkefPfe5zve2m2/ptjK/8P9lkk6Vnn3027b333uVSPkpPXYxtQTYPNMHfWAaDAv3eVltt1eu+3hjGl5FMohx33HGj3fJerCN2H3744bTzzjvn+Orit/mr2T59bXXQUSLvtp8t7+0jjjiiRJGOP/74NOWUU+a++4EHHhjjnaHf985BAlbfJwsssEA68sgje+OpO/GuL3G77/344Q9/OI8ppp566rzBLdK6yBe/+MXe8CUv+qZSt/qr6nuoPOfYDPux1X9X8+J8bPQLzcpc8uK9pd/45Cc/Odrvs5/9bIILvVx44YXTqFGjEuzHxfu15DWOgUAg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V7/61d7ydVLeujiq1xBZn/rUp9InP/nJ0X6f/exnM7klvYUXXjiNGjUqffGLX6zFabbZZkvHHntsQlxWxbP33Xdf9VI+f/jhh9POO++cybj3ve996Y033khbbbVVLzmnPTz//PNpjz32GONZhPSkk06adtppp9576lwe3nvvvXztl7/8ZW4r999/f9p1113TlFNOORrB87Wvfa332gMPPJDDam/Vn3aGAKxeW2CBBdKRRx6Z06BHs84662j3hf3Rj37Um69y0gyfcr/xiHBCOI5r+fvf/z4abnX5+f3vf58OP/zwulvpAx/4QCb5jzvuuNr7Ls4777xp9913H4OgU8/VNto0gn7c0P7oDl2vk5dffjmtu+66dbdyf136zG7rtzbCnouPP/547pvmmWeepG0feOCBve+Ev/zlL+njH/947jsc69pVs3jjeiAQCAQCgUAgEAgEAoFAIBAIBAJ9RWDIkmsIAETGlVdemY4//vjEE2u11VZL+++//xhG6umnn55uu+22thggIXbZZZds7Au84oorZoIOuTCcZNVVV0133nlnyywjMJoJo/Scc85Jrcq96aabpt12263X60RcSMg55phjUI15+ebJc9ZZZ2VybLrppkvbb799bVEWW2yxpkb9hBNO2PtMJ+XtDdzkBFGFzD355JPTzDPPnBZffPF8ftRRRyVpwQXZ99vf/jbxcDzssMPGiGmyySZLl19+eSbJqjcRF+JslGoZ3EOArbXWWhmfEhaxgKhCflUFUbbUUkul3/3ud5mMQ3LJ27PPPpvzJ+z111+f2wLiphDW1Tiq5//617/yvwi3iy++OP/OO++8NM000/SSdd/73vfSBRdckJDW3/jGNzIeHkKYarvlOWGWXXbZavT5vBk+YwT87wWE41CQffbZJ9GDVtIqr1WysVk9IODgZ8KhKshW1wdTePdOPvnkTQlEJO+ee+5Zm4WqDndbv7UR9lyE1yqrrJK9+fRjxxxzTNY1JKP2sc4666Tnnnsuk+Gf+cxnBrW/apbHuB4IBAKBQCAQCAQCgUAgEAgEAiMLgYmGanERAXfddVf2TOK9Q37+85+nD33oQ+mkk07Kxns174z+4iFRvV7OefgwEs8444xyKR833HDD0f6/4447Eg81hpoloIgHS7YYt0UYwIgOXhFIly9/+cvpc5/7XNpvv/2yQffRj340B0UK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)
어웨이 최근 3경기
def away_match_qs(self):
away_match_qs = \
self.team2_match.select_related('team1', 'team2').\
only("team1__name", "team2__name",
"team1_score", "team2_score", "date")[0:3]
return away_match_qs
실제 실행되는 sql
-> 필요한 필드만 가져오므로써 시간이 약 50% 정도 줄어든 것을 확인할 수 있었습니다.
2. Sql 을 줄이기.
팀 경기수
전체 경기수
total_match_count = Match.objects.filter(Q(team1_id=self.id) | Q(team2_id=self.id)).count()
승리 경기수
win_match_count = Match.objects.filter(winner_id=self.id).all().count()
패배 경기수
lose_match_count = total_match_count - win_match_count
record = {
"win_rate": (win_match_count / total_match_count) * 100,
"win": win_match_count,
"lose": lose_match_count
}
서로 다른 조건의 row 들을 가져오기 때문에 장고 ORM에서 제공되는 query로 한 번에 가져올 수 있는 방법이 떠오르질 않았습니다. 전체 경기를 가져온 후에 DB 에서가 아닌 파이썬 단에서 처리 할 수 있겠지만 코드 가독성면이나 성능면이나 좋은 방법이 아니라고 생각되어 이부분은 그대로 유지하기로 했습니다. 혹시, 좋은 방법이 있다면 댓글이나 연락 주시면 정말 감사하겠습니다.
참가한 대회 리스트
맨 위의 사진에서는 참가한 대회가 하나여서 몰랐지만 참가한 대회가 여러개인 경우 다음과 같이 참가한 대회수만큼 sql 인 실행되고 있는 것을 확인할 수 있었습니다.
장고 query
join_season = self.jointeam_set.all()
실제 실행되는 sql
중복 query가 실제 수행되는 코드
변경 후 코드
장고 query
def join_season(self):
join_season = self.jointeam_set.select_related("season").values_list("season")
return join_season
실제 실행되는 sql
실행되는 sql의 수를 해당 팀이 참여한 대회 수만큼 줄일 수 있었습니다. 한가지 추가적으로 말씀드리면 values_list 메서드와 only 메서드의 차이는 only 메서드의 id 를 따로 명시해주지 않아도 자동으로 가져오고, values_list 메서드의 경우 id 또한 명시적으로 지정해줘야 id column을 가져옵니다.
+ 추가적으로 시도해본 부분.
홈 경기평균 득점
home_average_score = Match.objects.filter(team1_id=self.id).\
aggregate(team1_score=Avg('team1_score'))['team1_score']
어웨이 경기 평균 득점
away_average_score = Match.objects.filter(team2_id=self.id).\
aggregate(team2_score=Avg('team2_score'))['team2_score’]
위 부분을 team을 가져올 때
View 단에서 아래와 같이 수정하고 아래와 같이 가져오면 sql 이 2개가 실행 될 줄 알고( prefetch -> select 해서 가져온 후에 다시 select from where in()) 해봤으나 오히려 prefetch_related 실행시 발생하는 추가 select sql 만 추가되어서 2개 sql 이 필요했고, AVG 문은 따로 수행이 되었다. 당연하게 aggregate 부분이 sql 단에서 처리가 되기 때문에 당연한 부분이었는데…
team = Team.objects.filter(id=pk).\
prefetch_related("team1_match", "team2_match").first()
def home_average_score(self):
home_average_score = self.team1_match.aggregate(team1_score=Avg('team1_score'))['team1_score']
# Match.objects.filter(team1_id=self.id).\
# aggregate(team1_score=Avg('team1_score'))['team1_score']
return home_average_score
수정 후 결과
실행시간: 6.32 ms, 총 sql수 :10 (컴퓨터 환경에 따라서 달라지지만 확실히 처음보다는 단축된 것을 확인했습니다.)
-> 만족스러운 결과는 아니지만 시간을 단축할 수 있었습니다. 1:1 매치의 특성상 두 번씩 가져오는 부분을 잘 수정해보고 싶은데 쉽지가 않네요. (DB 설계를 할 때 query를 효율적으로 가져올 수 있도록 설계가 필요할 것 같고, 장고 ORM과 SQL공부가 더 필요할 것 같습니다.)
-> 서비스가 발전한다면 Redis 같은 infra를 사용해서 응답속도를 줄여봐야 할 것 습니다.
저 혼자 열심히 해봤지만 한계가 많이 있는 것 같습니다.
많은 분들의 조언이 필요합니다!
혹시, 위과 같은 코드에서 응답시간을 줄일 수 있는 부분을 알고 계시는 분은 꼭 댓글이나 email(qoentlr37@gmail.com) 로 알려주시면 감사할 것 습니다 🙂
부족한 글 읽어주셔서 감사합니다!
반응형
'Computer Engineering > Django' 카테고리의 다른 글
Django DB Transaction 3편 - DB Transaction Test 코드 작성하기. (0) | 2022.02.02 |
---|---|
Django DB Transaction 2편 - 명시적으로 transaction 활용하기. (feat. savepoint) (2) | 2022.01.17 |
Django DB Transaction 1편 - Request와 DB Transaction 묶기(Feat. ATOMIC_REQUESTS) (0) | 2022.01.01 |
Django 오픈소스 contributing 하기! (3) | 2021.07.31 |
같은 Django model class에서 다른 schema가?(feat. Django는 이중인격?) (9) | 2021.01.25 |