Tune performance

- Added threading to gunicorn instance
- Changed cache to cachetools's TTL cache since it uses LRU if
the cache is full
- Increased cache sizes 1.5x
This commit is contained in:
Laszlo Zeke 2020-09-04 22:36:19 +02:00
parent 5fccffc2f0
commit e303af090d
4 changed files with 18 additions and 21 deletions

2
.gitignore vendored
View File

@ -100,3 +100,5 @@ $RECYCLE.BIN/
# Windows shortcuts # Windows shortcuts
*.lnk *.lnk
*.gcloudignore

View File

@ -1,6 +1,6 @@
runtime: python38 runtime: python38
entrypoint: gunicorn -b :$PORT twitchrss:app entrypoint: gunicorn -b :$PORT -k gthread --threads 2 twitchrss:app
env_variables: env_variables:
TWITCH_CLIENT_ID: __INSERT_TWITCH_CLIENT_ID_HERE__ TWITCH_CLIENT_ID: __INSERT_TWITCH_CLIENT_ID_HERE__

View File

@ -1,5 +1,6 @@
appdirs==1.4.3 appdirs==1.4.3
CacheControl==0.12.6 CacheControl==0.12.6
cachetools==4.1.1
certifi==2019.11.28 certifi==2019.11.28
chardet==3.0.4 chardet==3.0.4
click==7.1.2 click==7.1.2
@ -7,7 +8,6 @@ colorama==0.4.3
contextlib2==0.6.0 contextlib2==0.6.0
distlib==0.3.0 distlib==0.3.0
distro==1.4.0 distro==1.4.0
expiringdict==1.2.1
Flask==1.1.2 Flask==1.1.2
gunicorn==20.0.4 gunicorn==20.0.4
html5lib==1.0.1 html5lib==1.0.1

View File

@ -22,14 +22,14 @@ import logging
import re import re
from os import environ from os import environ
from feedformatter import Feed from feedformatter import Feed
from expiringdict import ExpiringDict from cachetools import cached, TTLCache, LRUCache
from io import BytesIO from io import BytesIO
import gzip import gzip
VOD_URL_TEMPLATE = 'https://api.twitch.tv/kraken/channels/%s/videos?broadcast_type=archive,highlight,upload&limit=10' VOD_URL_TEMPLATE = 'https://api.twitch.tv/kraken/channels/%s/videos?broadcast_type=archive,highlight,upload&limit=10'
USERID_URL_TEMPLATE = 'https://api.twitch.tv/kraken/users?login=%s' USERID_URL_TEMPLATE = 'https://api.twitch.tv/kraken/users?login=%s'
VODCACHE_LIFETIME = 600 VODCACHE_LIFETIME = 10 * 60
USERIDCACHE_LIFETIME = 24 * 60 * 60 USERIDCACHE_LIFETIME = 24 * 60 * 60
CHANNEL_FILTER = re.compile("^[a-zA-Z0-9_]{2,25}$") CHANNEL_FILTER = re.compile("^[a-zA-Z0-9_]{2,25}$")
TWITCH_CLIENT_ID = environ.get("TWITCH_CLIENT_ID") TWITCH_CLIENT_ID = environ.get("TWITCH_CLIENT_ID")
@ -41,9 +41,6 @@ if not TWITCH_CLIENT_ID:
app = Flask(__name__) app = Flask(__name__)
vodcache = ExpiringDict(max_len=200, max_age_seconds=VODCACHE_LIFETIME)
useridcache = ExpiringDict(max_len=1000, max_age_seconds=USERIDCACHE_LIFETIME)
@app.route('/vod/<string:channel>', methods=['GET', 'HEAD']) @app.route('/vod/<string:channel>', methods=['GET', 'HEAD'])
def vod(channel): def vod(channel):
@ -63,31 +60,29 @@ def vodonly(channel):
def get_inner(channel, add_live=True): def get_inner(channel, add_live=True):
userid_json = fetch_userid(channel) userid_json = fetch_userid(channel)
if not userid_json:
abort(404)
(channel_display_name, channel_id) = extract_userid(json.loads(userid_json)) (channel_display_name, channel_id) = extract_userid(json.loads(userid_json))
channel_json = fetch_vods(channel_id) channel_json = fetch_vods(channel_id)
if not channel_json:
abort(404)
decoded_json = json.loads(channel_json) decoded_json = json.loads(channel_json)
rss_data = construct_rss(channel, decoded_json, channel_display_name, add_live) rss_data = construct_rss(channel, decoded_json, channel_display_name, add_live)
headers = {'Content-Type': 'application/rss+xml'} headers = {'Content-Type': 'application/rss+xml'}
return rss_data, headers return rss_data, headers
@cached(cache=TTLCache(maxsize=2000, ttl=USERIDCACHE_LIFETIME))
def fetch_userid(channel_name): def fetch_userid(channel_name):
return fetch_or_cache_object(channel_name, useridcache, USERID_URL_TEMPLATE) return fetch_json(channel_name, USERID_URL_TEMPLATE)
@cached(cache=TTLCache(maxsize=400, ttl=VODCACHE_LIFETIME))
def fetch_vods(channel_id): def fetch_vods(channel_id):
return fetch_or_cache_object(channel_id, vodcache, VOD_URL_TEMPLATE) return fetch_json(channel_id, VOD_URL_TEMPLATE)
def fetch_or_cache_object(key, cachedict, url_template):
json_data = cachedict.get(key)
if not json_data:
json_data = fetch_json(key, url_template)
if not json_data:
abort(404)
else:
cachedict[key] = json_data
return json_data
def fetch_json(id, url_template): def fetch_json(id, url_template):
@ -112,7 +107,7 @@ def fetch_json(id, url_template):
except Exception as e: except Exception as e:
logging.warning("Fetch exception caught: %s" % e) logging.warning("Fetch exception caught: %s" % e)
retries += 1 retries += 1
return None abort(503)
def extract_userid(user_info): def extract_userid(user_info):