কেন একটি বিশাল জ্যাঙ্গো ক্যোয়ারীসেটের মাধ্যমে পুনরাবৃত্তি করা হচ্ছে প্রচুর পরিমাণে স্মৃতি?


111

প্রশ্নের সারণীতে প্রায় দশ মিলিয়ন সারি রয়েছে।

for event in Event.objects.all():
    print event

এর ফলে মেমরির ব্যবহার অবিচ্ছিন্নভাবে 4 গিগাবাইট বা তত বাড়তে পারে যার ফলে সারিগুলি দ্রুত মুদ্রণ করে। প্রথম সারির মুদ্রণের আগে দীর্ঘ দেরি আমাকে অবাক করেছিল - আমি আশা করি এটি প্রায় তাত্ক্ষণিকভাবে মুদ্রণ হবে to

আমিও চেষ্টা করেছি Event.objects.iterator()যা একইভাবে আচরণ করে।

আমি বুঝতে পারি না জাঙ্গো কী স্মৃতিতে লোড করছে বা কেন এটি করছে। আমি প্রত্যাশা করেছিলাম যে ডায়েঙ্গো ডাটাবেস পর্যায়ে ফলাফলগুলি পুনরাবৃত্তি করবে, যার অর্থ ফলাফলগুলি প্রায় স্থির হারে মুদ্রণ করা হবে (দীর্ঘ প্রতীক্ষার পরে একবারে না হয়ে)।

আমি কী ভুল বুঝেছি?

(এটি প্রাসঙ্গিক কিনা তা আমি জানি না তবে আমি পোস্টগ্রিজ এসকিউএল ব্যবহার করছি))


6
ছোট মেশিনগুলিতে এটি সরাসরি জাজানো শেল বা সার্ভারে "হত্যা" করতে পারে
স্টেফানো

উত্তর:


113

নাট সি খুব কাছে ছিল, তবে বেশ নয়।

ডক্স থেকে :

আপনি নিম্নলিখিত উপায়ে একটি ক্যোয়ারীসেট মূল্যায়ন করতে পারেন:

  • পুনরাবৃত্তির। একটি ক্যোরিসেটটি পুনরাবৃত্তিযোগ্য এবং এটি আপনার ডাটাবেস ক্যোয়ারিকে প্রথমবার পুনরাবৃত্তি করে exec উদাহরণস্বরূপ, এটি ডাটাবেসে সমস্ত প্রবেশের শিরোনামটি মুদ্রণ করবে:

    for e in Entry.objects.all():
        print e.headline

সুতরাং আপনার দশ মিলিয়ন সারি পুনরুদ্ধার করা হবে, একবারে আপনি যখন প্রথমে সেই লুপটি প্রবেশ করেন এবং ক্যোরিসেটটির পুনরাবৃত্তি ফর্মটি পান। আপনি যে অপেক্ষাটি অনুভব করছেন তা হ'ল জাজানো হ'ল ডাটাবেস সারি লোড করা এবং প্রত্যেকের জন্য অবজেক্ট তৈরি করা, এমন কোনও কিছু ফিরিয়ে দেওয়ার আগে যা আপনি আসলে পুনরাবৃত্তি করতে পারেন। তারপরে আপনার কাছে মেমরির সমস্ত কিছুই রয়েছে এবং ফলাফলগুলি ছড়িয়ে পড়বে।

আমার দস্তাবেজগুলি পড়ার থেকে, iterator() ক্যোরিসেটের অভ্যন্তরীণ ক্যাচিং প্রক্রিয়াগুলি বাইপাস করা ছাড়া আর কিছুই করে না। আমি মনে করি এটি একের পর এক কাজ করার জন্য এটি বোধগম্য হতে পারে, তবে এর জন্য আপনার ডাটাবেজে দশ মিলিয়ন স্বতন্ত্র হিট প্রয়োজন। সম্ভবত যে সমস্ত কাঙ্ক্ষিত না।

বড় ডেটাসেটের উপর দক্ষতার সাথে আইট্রেইন করা এমন একটি বিষয় যা আমরা এখনও বেশ সঠিকভাবে অর্জন করতে পারি নি, তবে এখানে কয়েকটি স্নিপেট রয়েছে যা আপনি আপনার উদ্দেশ্যে কার্যকর পেতে পারেন:


1
মহান উত্তরটির জন্য ধন্যবাদ, @ ইন্টারনকোড শেষ পর্যন্ত কাঙ্ক্ষিত ডাটাবেস-স্তর পুনরাবৃত্তির জন্য কাঁচা এসকিউএল এ নামলাম।
ডেভিডচ্যাম্বারগুলি

2
@ ইন্টারনকোড সুন্দর উত্তর, শুধু এই ইস্যুটি আঘাত। জঙ্গোতে কি এরপরে কোনও সম্পর্কিত আপডেট রয়েছে?
Zólyomi István

2
জাজানো ১.১১ সাল থেকে দস্তাবেজগুলি বলে যে পুনরাবৃত্তকারী () সার্ভার সাইড কার্সার ব্যবহার করে না।
জেফ সি জনসন

42

দ্রুত বা সর্বাধিক দক্ষ নাও হতে পারে, তবে প্রস্তুত সমাধান হিসাবে কেন জ্যাঙ্গো কোরের প্যাগিনেটর এবং পৃষ্ঠাগুলি এখানে ডকুমেন্টেড ব্যবহার করবেন না:

https://docs.djangoproject.com/en/dev/topics/pagination/

এটার মতো কিছু:

from django.core.paginator import Paginator
from djangoapp.models import model

paginator = Paginator(model.objects.all(), 1000) # chunks of 1000, you can 
                                                 # change this to desired chunk size

for page in range(1, paginator.num_pages + 1):
    for row in paginator.page(page).object_list:
        # here you can do whatever you want with the row
    print "done processing page %s" % page

3
পোস্টের পর থেকে এখন ছোট ছোট উন্নতি। বয়লারপ্লেট এড়াতে Paginatorএখন একটি page_rangeসম্পত্তি আছে। যদি সর্বনিম্ন মেমরির ওভারহেডের সন্ধানে থাকে তবে আপনি ব্যবহার করতে পারেন object_list.iterator()যা ক্যোরিসেট ক্যাশেটি জনপ্রিয় করবে নাprefetch_related_objectsতারপরে
কেন কল্টন

28

জ্যাঙ্গোর ডিফল্ট আচরণ হল ক্যোরিসেটের পুরো ফলাফলটিকে ক্যাশে করা যখন এটি কোয়েরিকে মূল্যায়ন করে। এই ক্যাচিং এড়ানোর জন্য আপনি ক্যোরিসেটের পুনরুক্তি পদ্ধতিটি ব্যবহার করতে পারেন:

for event in Event.objects.all().iterator():
    print event

https://docs.djangoproject.com/en/dev/ref/models/querysets/#iterator

পুনরুক্তি () পদ্ধতিটি ক্যোরিসেটটি মূল্যায়ন করে এবং তারপরে ক্যোরিসেট স্তরে ক্যাশে না করে সরাসরি ফলাফলগুলি পড়ে। আপনার কেবলমাত্র একবার অ্যাক্সেস করতে হবে এমন বিপুল সংখ্যক অবজেক্টের উপর পুনরাবৃত্তি করার সময় এই পদ্ধতিটি আরও ভাল পারফরম্যান্স এবং স্মৃতিতে উল্লেখযোগ্য হ্রাস পেতে পারে। নোট করুন যে ক্যাচিং এখনও ডাটাবেস পর্যায়ে করা হয়।

পুনরুক্তি () ব্যবহার করা আমার জন্য মেমরির ব্যবহার হ্রাস করে তবে এটি আমার প্রত্যাশার চেয়ে বেশি। এমপিএফ দ্বারা প্রস্তাবিত প্যাগিনেটর পদ্ধতির ব্যবহার করা অনেক কম স্মৃতি ব্যবহার করে, তবে আমার পরীক্ষার ক্ষেত্রে 2-3x ধীর হয়।

from django.core.paginator import Paginator

def chunked_iterator(queryset, chunk_size=10000):
    paginator = Paginator(queryset, chunk_size)
    for page in range(1, paginator.num_pages + 1):
        for obj in paginator.page(page).object_list:
            yield obj

for event in chunked_iterator(Event.objects.all()):
    print event

8

এটি ডক্স থেকে: http://docs.djangoproject.com/en/dev/ref/models/querysets/

আপনি ক্যুরিসেটটি মূল্যায়নের জন্য কিছু না করা পর্যন্ত কোনও ডাটাবেস ক্রিয়াকলাপ ঘটে না।

সুতরাং যখন print eventক্যোয়ারি চালিত হয় (যা আপনার আদেশ অনুসারে একটি সম্পূর্ণ টেবিল স্ক্যান)) এবং ফলাফলগুলি লোড করে। আপনার সমস্ত অবজেক্টের জন্য জিজ্ঞাসা করা হয়েছে এবং সেগুলি না পেয়ে প্রথম অবজেক্ট পাওয়ার কোনও উপায় নেই।

তবে আপনি যদি এমন কিছু করেন:

Event.objects.all()[300:900]

http://docs.djangoproject.com/en/dev/topics/db/queries/#limiting-querysets

তারপরে এটি অভ্যন্তরীণভাবে স্কেলটিতে অফসেট এবং সীমা যুক্ত করবে।


7

বড় পরিমাণে রেকর্ডের জন্য, একটি ডাটাবেস কার্সার আরও ভাল সম্পাদন করে। জ্যাঙ্গোতে আপনার কাঁচা এসকিউএল প্রয়োজন, জ্যাঙ্গো-কার্সার একটি এসকিউএল কার্সার থেকে আলাদা কিছু।

নেট সি দ্বারা প্রস্তাবিত লিমিট - অফসেট পদ্ধতি আপনার পরিস্থিতির জন্য যথেষ্ট ভাল হতে পারে। প্রচুর পরিমাণে ডেটার জন্য এটি কার্সারের চেয়ে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে না পেয়ে যায়।


4
ফ্র্যাঙ্ক, এটি অবশ্যই একটি ভাল পয়েন্ট তবে সমাধানের দিকে ঝুঁকতে কিছু কোডের বিবরণ দেখতে ভাল লাগবে ;-) (ভাল এই প্রশ্নটি এখন বেশ পুরানো ...)
স্টেফানো

7

জ্যাঙ্গোর কাছে ডাটাবেস থেকে বড় আইটেম আনার জন্য ভাল সমাধান নেই।

import gc
# Get the events in reverse order
eids = Event.objects.order_by("-id").values_list("id", flat=True)

for index, eid in enumerate(eids):
    event = Event.object.get(id=eid)
    # do necessary work with event
    if index % 100 == 0:
       gc.collect()
       print("completed 100 items")

ভ্যালু_লিস্টটি ডাটাবেসে সমস্ত আইডি আনতে এবং তারপরে প্রতিটি বস্তু আলাদাভাবে আনতে ব্যবহার করা যেতে পারে। সময়ের সাথে সাথে বড় আকারের বস্তু স্মৃতিতে তৈরি হবে এবং লুপের জন্য আবর্জনা সংগ্রহ করা হবে না ততক্ষণে বেরিয়ে আসা হবে। প্রতিটি 100 তম আইটেম খাওয়ার পরে উপরে কোড ম্যানুয়াল আবর্জনা সংগ্রহ করে।


স্ট্রিমিংএইচটিপিআরস্পোনস কী সমাধান হতে পারে? stackoverflow.com/questions/15359768/...
ratata

2
যাইহোক, এর ফলে লুপের সংখ্যা হিসাবে ডাটাবেসে সমান হিট আসবে, আমি আতঙ্কিত।
raratru

5

কারণ সেই উপায়ে পুরো ক্যোরিসেটের জন্য সমস্ত জিনিস একবারে মেমরিতে লোড হয়ে যায়। আপনাকে আপনার ক্যোয়ারসেটটি ছোট হজম বিটগুলিতে পরিণত করতে হবে। এটি করার প্যাটার্নটিকে চামচ খাওয়ানো বলা হয়। এখানে একটি সংক্ষিপ্ত বাস্তবায়ন।

def spoonfeed(qs, func, chunk=1000, start=0):
    ''' Chunk up a large queryset and run func on each item.

    Works with automatic primary key fields.

    chunk -- how many objects to take on at once
    start -- PK to start from

    >>> spoonfeed(Spam.objects.all(), nom_nom)
    '''
    while start < qs.order_by('pk').last().pk:
        for o in qs.filter(pk__gt=start, pk__lte=start+chunk):
            yeild func(o)
        start += chunk

এটি ব্যবহার করতে আপনি এমন একটি ফাংশন লিখুন যা আপনার অবজেক্টে অপারেশন করে:

def set_population_density(town):
    town.population_density = calculate_population_density(...)
    town.save()

এবং আপনার ক্যোয়ারসেটে সেই ফাংশনটি চালানোর চেয়ে:

spoonfeed(Town.objects.all(), set_population_density)

funcসমান্তরালে একাধিক বস্তুর উপর নির্বাহের জন্য মাল্টিপ্রসেসিংয়ের মাধ্যমে এটি আরও উন্নত করা যেতে পারে ।


1
দেখে মনে হচ্ছে এটি পুনরুক্তি (চঙ্ক_সাইজ = 1000) দিয়ে 1.12 এ নির্মিত হতে চলেছে
কেভিন পার্কার

3

এখানে লেন এবং গণনা সহ একটি সমাধান:

class GeneratorWithLen(object):
    """
    Generator that includes len and count for given queryset
    """
    def __init__(self, generator, length):
        self.generator = generator
        self.length = length

    def __len__(self):
        return self.length

    def __iter__(self):
        return self.generator

    def __getitem__(self, item):
        return self.generator.__getitem__(item)

    def next(self):
        return next(self.generator)

    def count(self):
        return self.__len__()

def batch(queryset, batch_size=1024):
    """
    returns a generator that does not cache results on the QuerySet
    Aimed to use with expected HUGE/ENORMOUS data sets, no caching, no memory used more than batch_size

    :param batch_size: Size for the maximum chunk of data in memory
    :return: generator
    """
    total = queryset.count()

    def batch_qs(_qs, _batch_size=batch_size):
        """
        Returns a (start, end, total, queryset) tuple for each batch in the given
        queryset.
        """
        for start in range(0, total, _batch_size):
            end = min(start + _batch_size, total)
            yield (start, end, total, _qs[start:end])

    def generate_items():
        queryset.order_by()  # Clearing... ordering by id if PK autoincremental
        for start, end, total, qs in batch_qs(queryset):
            for item in qs:
                yield item

    return GeneratorWithLen(generate_items(), total)

ব্যবহার:

events = batch(Event.objects.all())
len(events) == events.count()
for event in events:
    # Do something with the Event

0

আমি সাধারণত এই জাতীয় কাজের জন্য জ্যাঙ্গো ওআরএম এর পরিবর্তে কাঁচা মাইএসকিউএল কাঁচা ক্যোয়ারী ব্যবহার করি।

মাইএসকিউএল স্ট্রিমিং মোডকে সমর্থন করে যাতে মেমরির ত্রুটি না ছাড়াই আমরা নিরাপদে এবং দ্রুত সমস্ত রেকর্ডটি লুপ করতে পারি।

import MySQLdb
db_config = {}  # config your db here
connection = MySQLdb.connect(
        host=db_config['HOST'], user=db_config['USER'],
        port=int(db_config['PORT']), passwd=db_config['PASSWORD'], db=db_config['NAME'])
cursor = MySQLdb.cursors.SSCursor(connection)  # SSCursor for streaming mode
cursor.execute("SELECT * FROM event")
while True:
    record = cursor.fetchone()
    if record is None:
        break
    # Do something with record here

cursor.close()
connection.close()

সূত্র:

  1. মাইএসকিউএল থেকে কয়েক মিলিয়ন সারি পুনরুদ্ধার করা হচ্ছে
  2. মাইএসকিউএল ফলাফল সেট স্ট্রিমিং কীভাবে একবারে পুরো জেডিবিসি রেজাল্টসেট আনতে বনাম সম্পাদন করে

আপনি এখনও জিজ্ঞাসা তৈরি করতে জ্যাঙ্গো ওআরএম ব্যবহার করতে পারেন। queryset.queryআপনার মৃত্যুদন্ড কার্যকর করার জন্য ব্যবহার করুন ।
পোল
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.