在大(ish)数据集上缓慢django数据库操作。

时间:2015-06-21 20:46:38

标签: python django performance twitter django-1.6

我建立了一个系统来过滤twitter实时流样本。显然,数据库写入速度太慢,无法跟上比几个低容量关键字更复杂的事情。我实现了django-rq作为一个简单的排队系统,可以将推文推送到基于redis的队列中,并且效果很好。我的问题在另一方面。这个问题的背景是我有一个现在正在运行的系统,有150万条推文用于分析,另有375,000条通过redis排队。按目前的表现速度,如果我关闭了我不想要的流,我需要花上3天时间才能赶上。如果我维持这些流,那么根据我的最后估计,它将花费大约一个月的时间。

现在,数据库在两个主表上有几百万行,写入速度非常慢。 rq-worker的最佳数量似乎是4,并且平均每秒1.6个队列任务。 (以下列入的代码)。我认为可能问题是为每个新队列任务打开了数据库连接,所以把CONN_MAX_AGE放到60,但是没有改进任何东西。

刚刚在localhost上测试过这个问题,我在Macbook 2011上运行时已超过13次/秒,Chrome等运行,但该数据库中只有几千行,这让我相信它& #39;尺寸相关。我使用了几个get_or_create命令(见下文),这可能会减慢速度,但无法通过其他任何方式使用它们 - 我需要检查用户是否存在,我需要检查推文是否已经存在(我怀疑,我可能会将后者移到try / except,因为来自直播的推文不应该已经存在,显而易见理由。)我会从中获得多少性能提升吗?由于这仍在运行,我热衷于优化代码并在那里获得更快/更高效的工作人员,以便我能赶上!是否会运行预先审查的工作人员批量处理工作? (即我可以批量创建不存在的用户,或类似的东西?)

我在数字海洋上运行4 Core / 8Gb Ram液滴,所以觉得这是一个非常可怕的性能,并且可能与代码相关。我在哪里错了?
(我在这里发布了这个而不是代码审查,因为我认为这与SO的Q& A格式相关,因为我试图解决特定的代码问题,而不是'我怎么能更好地做到这一点?')

注意:我在django 1.6工作,因为这是我已经流传一段时间的代码,并且对当时的升级没有信心 - 它不是面向公众的,所以除非现在有令人信服的理由(比如这个性能问题),否则我不会升级(对于这个项目)。

流式监听器:

class StdOutListener(tweepy.StreamListener):
            def on_data(self, data):
                # Twitter returns data in JSON format - we need to decode it first
                decoded = json.loads(data)
                #print type(decoded), decoded
                # Also, we convert UTF-8 to ASCII ignoring all bad characters sent by users
                try:
                    if decoded['lang'] == 'en':
                        django_rq.enqueue(read_both, decoded)
                    else:
                        pass
                except KeyError,e:
                    print "Error on Key", e
                except DataError, e:
                    print "DataError", e
                return True

            def on_error(self, status):
                print status

读取用户/推文/两者

def read_user(tweet):
    from harvester.models import User
    from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
    #We might get weird results where user has changed their details"], so first we check the UID.
    #print "MULTIPLE USER DEBUG", tweet["user"]["id_str"]
    try:
        current_user = User.objects.get(id_str=tweet["user"]["id_str"])
        created=False
        return current_user, created
    except ObjectDoesNotExist:
        pass
    except MultipleObjectsReturned:
        current_user = User.objects.filter(id_str=tweet["user"]["id_str"])[0]
        return current_user, False
    if not tweet["user"]["follow_request_sent"]:
        tweet["user"]["follow_request_sent"] = False
    if not tweet["user"]["following"]:
        tweet["user"]["following"] = False
    if not tweet["user"]["description"]:
        tweet["user"]["description"] = " "
    if not tweet["user"]["notifications"]:
        tweet["user"]["notifications"] = False

    #If that doesn't work"], then we'll use get_or_create (as a failback rather than save())
    from dateutil.parser import parse
    if not tweet["user"]["contributors_enabled"]:
        current_user, created = User.objects.get_or_create(
            follow_request_sent=tweet["user"]["follow_request_sent"],
            _json = {},
            verified = tweet["user"]["verified"],
            followers_count = tweet["user"]["followers_count"],
            profile_image_url_https = tweet["user"]["profile_image_url_https"],
            id_str = tweet["user"]["id_str"],
            listed_count = tweet["user"]["listed_count"],
            utc_offset = tweet["user"]["utc_offset"],
            statuses_count = tweet["user"]["statuses_count"],
            description = tweet["user"]["description"],
            friends_count = tweet["user"]["friends_count"],
            location = tweet["user"]["location"],
            profile_image_url= tweet["user"]["profile_image_url"],
            following = tweet["user"]["following"],
            geo_enabled = tweet["user"]["geo_enabled"],
            profile_background_image_url =tweet["user"]["profile_background_image_url"],
            screen_name = tweet["user"]["screen_name"],
            lang =  tweet["user"]["lang"],
            profile_background_tile = tweet["user"]["profile_background_tile"],
            favourites_count = tweet["user"]["favourites_count"],
            name = tweet["user"]["name"],
            notifications = tweet["user"]["notifications"],
            url = tweet["user"]["url"],
            created_at = parse(tweet["user"]["created_at"]),
            contributors_enabled = False,
            time_zone = tweet["user"]["time_zone"],
            protected = tweet["user"]["protected"],
            default_profile = tweet["user"]["default_profile"],
            is_translator = tweet["user"]["is_translator"]
        )
    else:
        current_user, created = User.objects.get_or_create(
            follow_request_sent=tweet["user"]["follow_request_sent"],
            _json = {},
            verified = tweet["user"]["verified"],
            followers_count = tweet["user"]["followers_count"],
            profile_image_url_https = tweet["user"]["profile_image_url_https"],
            id_str = tweet["user"]["id_str"],
            listed_count = tweet["user"]["listed_count"],
            utc_offset = tweet["user"]["utc_offset"],
            statuses_count = tweet["user"]["statuses_count"],
            description = tweet["user"]["description"],
            friends_count = tweet["user"]["friends_count"],
            location = tweet["user"]["location"],
            profile_image_url= tweet["user"]["profile_image_url"],
            following = tweet["user"]["following"],
            geo_enabled = tweet["user"]["geo_enabled"],
            profile_background_image_url =tweet["user"]["profile_background_image_url"],
            screen_name = tweet["user"]["screen_name"],
            lang =  tweet["user"]["lang"],
            profile_background_tile = tweet["user"]["profile_background_tile"],
            favourites_count = tweet["user"]["favourites_count"],
            name = tweet["user"]["name"],
            notifications = tweet["user"]["notifications"],
            url = tweet["user"]["url"],
            created_at = parse(tweet["user"]["created_at"]),
            contributors_enabled = tweet["user"]["contributers_enabled"],
            time_zone = tweet["user"]["time_zone"],
            protected = tweet["user"]["protected"],
            default_profile = tweet["user"]["default_profile"],
            is_translator = tweet["user"]["is_translator"]
        )
    #print "CURRENT USER:""], type(current_user)"], current_user
    #current_user"], created = User.objects.get_or_create(current_user)
    return current_user, created

def read_tweet(tweet, current_user):
    import logging
    logger = logging.getLogger('django')
    from datetime import date, datetime
    #print "Inside read_Tweet"
    from harvester.models import Tweet
    from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
    from django.db import DataError
    #We might get weird results where user has changed their details"], so first we check the UID.
    #print tweet_data["created_at"]
    from dateutil.parser import parse
    tweet["created_at"] = parse(tweet["created_at"])
    try:
        #print "trying tweet_data["id"
        current_tweet =Tweet.objects.get(id_str=tweet["id_str"])
        created=False
        return current_user, created
    except ObjectDoesNotExist:
        pass
    except MultipleObjectsReturned:
        current_tweet =Tweet.objects.filter(id_str=tweet["id_str"])[0]
    try:
        current_tweet, created = Tweet.objects.get_or_create(
        truncated=tweet["truncated"],
        text=tweet["text"],
        favorite_count=tweet["favorite_count"],
        author = current_user,
        _json = {},
        source=tweet["source"],
        retweeted=tweet["retweeted"],
        coordinates = tweet["coordinates"],
        entities = tweet["entities"],
        in_reply_to_screen_name = tweet["in_reply_to_screen_name"],
        id_str = tweet["id_str"],
        retweet_count = tweet["retweet_count"],
        favorited = tweet["favorited"],
        user = tweet["user"],
        geo = tweet["geo"],
        in_reply_to_user_id_str = tweet["in_reply_to_user_id_str"],
        lang = tweet["lang"],
        created_at = tweet["created_at"],
        place = tweet["place"])
        print "DEBUG", current_user, current_tweet
        return current_tweet, created
    except DataError, e:
        #Catchall to pick up non-parsed tweets
        print "DEBUG ERROR", e, tweet
        return None, False

def read_both(tweet):
    current_user, created = read_user(tweet)
    current_tweet, created = read_tweet(tweet, current_user)

1 个答案:

答案 0 :(得分:0)

我最终设法凑齐了一些redditors和其他一些东西的答案。

从根本上说,虽然我在id_str字段上进行了双重查找,但没有对其进行索引。我在db_index=Trueread_tweet上向该字段添加了索引read_user,并将阅读推文移至try / except Tweet.objects.create方法,如果有&#39则返回到get_or_create一个问题,并且看到了50-60倍的速度改进,工人现在可以扩展 - 如果我增加10个工人,我得到10倍的速度。

我目前有一名工作人员乐意处理6条左右的推文。接下来,我将添加一个监控守护进程来检查队列大小,并在其增加的情况下添加额外的工作人员。

tl; dr - 记住索引!