<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kyle Johnson</title>
    <description>The latest articles on DEV Community by Kyle Johnson (@kjpctech).</description>
    <link>https://dev.to/kjpctech</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F626768%2F0db16c77-0cc5-4237-9336-6f367b59b2f6.jpg</url>
      <title>DEV Community: Kyle Johnson</title>
      <link>https://dev.to/kjpctech</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kjpctech"/>
    <language>en</language>
    <item>
      <title>How to Name Django Migrations (and Why It's Important)</title>
      <dc:creator>Kyle Johnson</dc:creator>
      <pubDate>Fri, 25 Jun 2021 20:11:12 +0000</pubDate>
      <link>https://dev.to/kjpctech/how-to-name-django-migrations-and-why-it-s-important-37pi</link>
      <guid>https://dev.to/kjpctech/how-to-name-django-migrations-and-why-it-s-important-37pi</guid>
      <description>&lt;p&gt;Have you ever needed to undo, fake, or deal with migrations more deeply than the typical &lt;code&gt;python manage.py migrate&lt;/code&gt;? Did you find yourself opening the migration files to find out what they contained? Everybody probably will at some point. &lt;/p&gt;

&lt;p&gt;And if you have, you probably know what a pain it can be to go searching through all those files. In this post, we'll talk about the benefits of properly naming your Django migrations and show how doing a little prep work can save you -- and your fellow developers -- a lot of time. But before that, let's cover the basics. &lt;/p&gt;

&lt;h2&gt;
  
  
  What are Django Migrations?
&lt;/h2&gt;

&lt;p&gt;Django migrations are a core part of the Django Object-Relational Mapper, commonly shortened to ORM. If you’re unfamiliar with ORM, it’s one of Django’s powerful features which enables you to interact with your database, like you would with SQL. &lt;/p&gt;

&lt;p&gt;The migration framework was brought into Django on version 1.7. Migrations themselves consist of Python code and define the operations to apply to a database schema as it progresses through the Software Development Life Cycle, or SDLC. These migration files for each application live in a migrations directory within the app and, according to Django documentation, are designed to be committed to, and distributed as part of, its codebase. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://django.readthedocs.io/en/stable/topics/migrations.html"&gt;Django documentation&lt;/a&gt; tells us that migrations should be made “once on your development machine and then running the same migrations on your colleagues’ machines, your staging machines, and eventually your production machines.” &lt;/p&gt;

&lt;p&gt;It's helpful to think “of migrations as a version control system for your database schema.”&lt;/p&gt;

&lt;p&gt;In terms of backend support, migrations are supported on any backends that Django ships with, including third-party backends that have support for schema alteration. But not all databases are equal when it comes to migrations. Django’s documentation states that some are more capable than others and it’s worth understanding the differences.  &lt;/p&gt;

&lt;h3&gt;
  
  
  PostgreSQL
&lt;/h3&gt;

&lt;p&gt;In terms of schema support, PostgreSQL is deemed the most capable option available. The lone exception is versions before PostgreSQL 11, which added columns with default values, causing a full rewrite of the table, for a time proportional to its size. &lt;/p&gt;

&lt;p&gt;Django’s documentation recommends you always create new columns with null=True, as this way they will be added immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  MySQL
&lt;/h3&gt;

&lt;p&gt;Django’s migration documentation includes three suggestions when it comes to MySQL support. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Roll-Back Risks: MySQL lacks support for transactions around schema alteration operations. If a migration fails, you will have to manually unpick the changes in order to try again. In short, if you find yourself here, it’s impossible to roll back with Django to an earlier point -- for that you’ll need to use raw SQL.&lt;/li&gt;
&lt;li&gt;Slow Execution Times: MySQL fully rewrites tables for almost every schema operation, causing resource usage consideration because it takes time proportional to the number of rows in the table to add or remove columns. On slower hardware, this can really drag out the process to an estimated minute per million rows.  - adding a few columns to a table with just a few million rows could lock your site up for over ten minutes.&lt;/li&gt;
&lt;li&gt;Limited Name Lengths: MySQL has relatively small limits on name lengths for columns, tables and indexes, as well as a limit on the combined size of all columns an index covers. This means that indexes that are possible on other backends will fail to be created under MySQL.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  SQLite
&lt;/h3&gt;

&lt;p&gt;SQLite lacks a lot robust built-in schema alteration support, forcing Django to step in an emulate it by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating a new table with the new schema&lt;/li&gt;
&lt;li&gt;Copying the data across&lt;/li&gt;
&lt;li&gt;Dropping the old table&lt;/li&gt;
&lt;li&gt;Renaming the new table to match the original name&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While Django’s documentation is generally optimistic about how the process works in general, it also admits that results can come slowly and the outcome be “occasionally buggy”. It’s not recommended developers run and migrate SQLite in a production environment without first considering the risks and limitations. &lt;/p&gt;

&lt;h2&gt;
  
  
  What’s So Great About Django Migrations?
&lt;/h2&gt;

&lt;p&gt;Above nearly everything else, Django Migrations are important and helpful because they make developers' lives easier. &lt;/p&gt;

&lt;h3&gt;
  
  
  Django Migrations Help with Agility
&lt;/h3&gt;

&lt;p&gt;Databases are central components of an application and in today’s fast paced world, things change quickly. Agile projects are always changing and sometimes, adjustments are necessary to meet updated requirements. Django migrations help with the process of making, applying, and tracking changes to database schemas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Developers Use Python for Writing Django Migrations
&lt;/h3&gt;

&lt;p&gt;If you are working with Django then you are more than likely adept at coding in Python because the two go hand-in-hand. In Django, Migrations are written (primarily) in Python -- a language the developers know anyway -- so you’ll be working with a language you’re familiar with anyway.  &lt;/p&gt;

&lt;p&gt;Without Migrations, developers would have to have to learn SQL commands or work with a GUI like Phpmyadmin every time they wanted to change the model definition and modify the database schema.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Django Migrations Help Reduce Repetition
&lt;/h3&gt;

&lt;p&gt;Migrations are generated from the models you create so the process doesn’t have to be repeated. In comparison, building a model and then writing SQL to generate database tables is repetitive.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Django Migrations Benefit from Version Control (Git)
&lt;/h3&gt;

&lt;p&gt;Database schemas don’t live in the code but Django Migrations are housed in the application, right in a folder with an appropriate name. So when you commit changes and make updates to the Migration, the changes are tracked. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why Should Developers Consider Naming Migrations?
&lt;/h2&gt;

&lt;p&gt;One of the really helpful features of Django Migrations is that they can be named. You can attach a descriptive title to each migration in an effort to distinguish it from others. And if your project becomes complex and goes on for an extended period of time, there can be several migrations. &lt;/p&gt;

&lt;p&gt;Now, there’s absolutely no issue with deciding to not name migrations. You can certainly get by without doing it. &lt;/p&gt;

&lt;p&gt;Still, taking a minute to name Django Migrations is helpful if it only gives you an indication as to what’s inside. Just as it’s worthwhile to be descriptive in naming commits, branches, and nearly anything when it comes to version control in Git, naming Django Migrations can help you when you’re looking back through versions for a specific file. &lt;/p&gt;

&lt;p&gt;You can either spend your time going through files one-by-one until you find what you’re looking for or you can see a list of properly named Django Migrations and get a good idea of where something is at first glance. &lt;/p&gt;

&lt;p&gt;Clean code makes for easier maintenance. It just makes life a little easier. &lt;/p&gt;

&lt;h2&gt;
  
  
  How Do You Name Django Migrations?
&lt;/h2&gt;

&lt;p&gt;Django’s makemigrations command has a flag &lt;code&gt;--name&lt;/code&gt; that can be used to make migrations more readable and easy on the eyes. &lt;/p&gt;

&lt;p&gt;Sometimes Django will name your migrations for you but when it doesn't, the resulting title can be unhelpful when read by human beings. When Django names migrations, it comes out looking like this: &lt;code&gt;0005_auto_20210608_2154&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;What does that file contain? Who knows. Important stuff? Maybe. Non-essential parts? Your guess is as good as mine. &lt;/p&gt;

&lt;p&gt;Alternatively, what if you took the time to name a migration like this:  &lt;code&gt;0005_person_email_and_opt_out&lt;/code&gt;? Even for someone unfamiliar with the project will probably be able to figure out what that migration contains.&lt;/p&gt;

&lt;p&gt;Here’s a simple example I made. The first list only used python manage.py makemigrations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0001_initial&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0002_auto_20210608_2147&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0003_auto_20210608_2149&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0004_person_phone_number&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0005_auto_20210608_2154&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0006_auto_20210608_2155&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now below are the exact same migrations but with the &lt;code&gt;--name&lt;/code&gt; flag included in the &lt;code&gt;makemigrations&lt;/code&gt; command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0001_initial&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0002_business_address_fields&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0003_business_owner_and_person&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0004_person_phone_number&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0005_person_email_and_opt_out&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0006_business_description_and_services&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Taking a small bit of time to name your migrations results in a more readable migration history which will make life easier in the future. &lt;/p&gt;

&lt;p&gt;Look at the internal Django apps as an example. Every migration is named.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;admin&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0001_initial&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0002_logentry_remove_auto_add&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0003_logentry_add_action_flag_choices&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;auth&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0001_initial&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0002_alter_permission_name_max_length&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0003_alter_user_email_max_length&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0004_alter_user_username_opts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0005_alter_user_last_login_null&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0006_require_contenttypes_0002&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0007_alter_validators_add_error_messages&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0008_alter_user_username_max_length&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0009_alter_user_last_name_max_length&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0010_alter_group_name_max_length&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0011_update_proxy_permissions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0012_alter_user_first_name_max_length&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;contenttypes&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0001_initial&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0002_remove_content_type_name&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's that easy. &lt;/p&gt;

&lt;p&gt;Django migrations go a long way in making a developer's life easier. But you can level up your game simply and make life easier for everyone when you give the migrations a descriptive, appropriate name. This post originally appeared on our &lt;a href="https://nextlinklabs.com/insights"&gt;insights blog&lt;/a&gt;, where we write about &lt;a href="https://www.nextlinklabs.com/services/devops-consulting-services"&gt;devops consulting services&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>database</category>
    </item>
    <item>
      <title>A Django Upgrade Guide for Major and Minor Releases</title>
      <dc:creator>Kyle Johnson</dc:creator>
      <pubDate>Fri, 07 May 2021 12:14:56 +0000</pubDate>
      <link>https://dev.to/kjpctech/a-django-upgrade-guide-for-major-and-minor-releases-367j</link>
      <guid>https://dev.to/kjpctech/a-django-upgrade-guide-for-major-and-minor-releases-367j</guid>
      <description>&lt;p&gt;The Django project has major updates every eight months and minor updates as needed. It's a good idea to keep your project up to date with the latest version or at least a supported version. This keeps your application secure and allows you to use new features like ASGI support, built-in cross-db JSON field, upcoming functional indexes, and more.&lt;/p&gt;

&lt;p&gt;With the upcoming Django 3.2 release in mind, this post goes through the general process for updating a Django project.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Upgrade to Minor Releases of Django
&lt;/h2&gt;

&lt;p&gt;Patch releases are made available as needed.&lt;/p&gt;

&lt;p&gt;These minor releases fix bugs and security issues in the major version. Always upgrade your projects to the latest patch of the major version you are running. It is easy and there is no reason not to do this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.djangoproject.com/en/dev/internals/release-process/"&gt;Per the Django documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;These releases will be 100% compatible with the associated feature release, unless this is impossible for security reasons or to prevent data loss. So the answer to "should I upgrade to the latest patch release?” will always be "yes."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Here's how to upgrade using the latest minor patch
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Read the release notes and pay attention to anything that might affect your project. Since the minor releases are “100% compatible with the associated feature release” there should be nothing you need to change in your project, with the exception of the next step below.&lt;/li&gt;
&lt;li&gt;Change Django version in requirements file. For example if you are running Django==3.1.6 and the 3.1.7 patch comes out you will update your requirements file to Django==3.1.7&lt;/li&gt;
&lt;li&gt;Install the updated requirements and test your project.&lt;/li&gt;
&lt;li&gt;That's it. You've completed the upgrade.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to Upgrade to Major Releases of Django
&lt;/h2&gt;

&lt;p&gt;Feature releases are made available every 8 months.&lt;/p&gt;

&lt;p&gt;Version 3.2 comes out in April of 2021 and is going to be a long term support (LTS) release.&lt;/p&gt;

&lt;p&gt;Every .2 release will be a LTS release starting with 2.2. Prior to 2.2 there LTS releases did not end in .2 (1.11, 1.8, etc).&lt;/p&gt;

&lt;p&gt;It is a good idea to keep up with the latest Django version even if it is not a LTS release. You get the latest security enhancements, features, and performance improvements. You also reduce technical debt of upgrading down the road when you are multiple versions behind.&lt;/p&gt;

&lt;h3&gt;
  
  
  Here's how to upgrade using the latest major release
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;First, add a source control system such as Git to your application if it isn’t already. This will allow you to maintain various versions of your application such as the old version and your work-in-progress upgraded project. If you need help, find an authorized &lt;a href="https://nextlinklabs.com/services/gitlab-professional-services-offerings"&gt;GitLab&lt;/a&gt; partner to make sure you're doing it right. &lt;/li&gt;
&lt;li&gt;Dockerize your application if it isn’t already. This isn’t necessary but it is extremely helpful when you are upgrading from an ancient version of Django (or Python 2, database, cache, etc). (If you are still on Django 1.1 you are not alone - I recently upgraded one of those.).
&lt;/li&gt;
&lt;li&gt;Test the project and add tests where they are lacking. &lt;a href="https://nextlinklabs.com/insights/automated-software-testing-benefits-for-every-company"&gt;Automated software tests are a lifesaver&lt;/a&gt; in keeping software up to date.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Run tests with python -Wall manage.py test and deal with any deprecation warnings&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Read the release notes for the major Django version that comes after the version you are currently running. Make any changes to your project. If any third party packages you are using are not compatible with the new major Django version, you will need to also read those release notes and update your project accordingly.&lt;/li&gt;
&lt;li&gt;Change your requirements file to use the new major Django version and any dependencies that need to be updated. Install the updated requirements.&lt;/li&gt;
&lt;li&gt;Run Django checks with python -Wall manage.py check&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Fix any issues and deal with any deprecation warnings&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Run tests with python -Wall manage.py test&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Fix any issues and deal with any deprecation warnings&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Repeat steps 4 through 10 until you get to the latest version of Django&lt;/li&gt;
&lt;li&gt;Upgrade all dependencies to supported versions. Basically, repeat steps 4 through 10 but with the third party packages instead of with Django.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  How Do I Upgrade If I'm on Python 2?
&lt;/h3&gt;

&lt;p&gt;If you are still on Python 2, it's time to jump ship and get on board with Python 3.&lt;/p&gt;

&lt;p&gt;The steps to update are the same but &lt;em&gt;first&lt;/em&gt;, upgrade to Django 1.11 while staying on Python 2, then switch to Python 3, test, and continue the Django upgrade to the latest version. Django 1.11 is the last Django version to support Python 2 and it is also the beginning of where Django upgrades become very stable.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if I’m using a Django version under 1.7 (Where migrations were added)?
&lt;/h3&gt;

&lt;p&gt;As with everything this can vary on a project-by-project basis but the simple version is upgrade to Django 1.7, delete any south migrations, generate new initial migrations that match the existing database schema, and then run them. If you are running the new migrations for the first time on Django 1.8 or greater then use the --fake-initial flag.&lt;/p&gt;

&lt;p&gt;Once the migrations are finished, continue upgrading Django to the latest version.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if my database is no longer supported by Django?
&lt;/h3&gt;

&lt;p&gt;First things first, backup your database(s). If the project is small enough then the simplest solution might be to use python manage.py dumpdata and python manage.py loaddata to copy the data from the old database version into a newer database.&lt;/p&gt;

&lt;p&gt;For larger projects refer to database specific guides for tools such a pg_upgrade or mysql_upgrade.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Upgrading software can be intimidating but there is no better time to do it than now since it only gets more difficult and insecure down the road. Tools such as source control and Docker help reduce environment differences and lead to a better development/deployment experience. Finally, automated testing is a massive help in preventing errors during regular development and software upgrades.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nextlinklabs.com/insights/django-upgrade-guide-for-major-and-minor-releases"&gt;This post&lt;/a&gt; originally appeared on our &lt;a href="https://nextlinklabs.com/services/devops-consulting-services"&gt;devops consulting&lt;/a&gt; &lt;a href="https://nextlinklabs.com/insights"&gt;blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>upgrade</category>
    </item>
    <item>
      <title>Improving Django View Performance with Async Support</title>
      <dc:creator>Kyle Johnson</dc:creator>
      <pubDate>Thu, 06 May 2021 19:52:05 +0000</pubDate>
      <link>https://dev.to/kjpctech/improving-django-view-performance-with-async-support-52lp</link>
      <guid>https://dev.to/kjpctech/improving-django-view-performance-with-async-support-52lp</guid>
      <description>&lt;p&gt;&lt;a href="https://docs.djangoproject.com/en/3.1/topics/async/" rel="noopener noreferrer"&gt;Django 3.1 was recently released and along with it came support for asynchronous views.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To anyone that uses Django and works with lots of data from lots of different Web sources, this is a big deal. Supporting asynchronous views means cleaner code, a different way to think about things, and most importantly, the ability to dramatically improve performance of applications.&lt;/p&gt;

&lt;p&gt;But let’s back up a bit.&lt;/p&gt;

&lt;p&gt;If you’re unfamiliar with the term “views”, don’t worry, it’s an easy concept to understand.&lt;/p&gt;

&lt;p&gt;Views are key components of applications built in with the Django framework. At their very simplest, views are Python functions or classes that take a web request and produce a web response. Prior to Django 3.1, views had to run with Python threads. Now, views can run in an asynchronous event loop without Python threads. This means Python’s asyncio library can be used inside of Django views.&lt;/p&gt;

&lt;p&gt;According to the &lt;a href="https://docs.djangoproject.com/en/3.1/topics/async/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;:&lt;/p&gt;

&lt;h4&gt;
  
  
  The main benefits are the ability to service hundreds of connections without using Python threads.
&lt;/h4&gt;

&lt;p&gt;There are other benefits as well including the use of Python’s &lt;a href="https://docs.python.org/3/library/asyncio-task.html#running-tasks-concurrently" rel="noopener noreferrer"&gt;asyncio.gather&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let’s say you have a view that makes four API calls. Even in a best case scenario, if each call takes only a second, it’s a total of four seconds if executed synchronously. And that’s the best case scenario.&lt;/p&gt;

&lt;p&gt;We can cut down on that time frame significantly and improve the situation overall by using Pythons concurrent.futures library.&lt;/p&gt;

&lt;p&gt;This makes it possible to make the four API calls in the previous example concurrently meaning the view could take roughly one second in total if using four workers with the ThreadPoolExecutor. By all accounts, the practice cuts down on the time needed and improves the calls.&lt;/p&gt;

&lt;p&gt;That’s important in a world where seconds matter and making someone wait around for an application to load can cause frustration.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Real-World Example: Tracking Montana's Yellowstone River
&lt;/h2&gt;

&lt;p&gt;To illustrate how asynchronous views improve performance, I created an example project to display statistical data from the &lt;a href="https://waterdata.usgs.gov/nwis" rel="noopener noreferrer"&gt;United States Geological Survey (USGS)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The project makes six API calls to the USGS to collect data about six access points on the Yellowstone River in my home state of Montana. This data includes the volume of water moving at each access point at the time, known as discharge rate, as well as the gage height, which is the surface level of the water relative to its streambed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 1: The Synchronous Method
&lt;/h3&gt;

&lt;p&gt;Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_river_flow_and_height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;site_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
  Synchronous method to get river data from USGS
  &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
  &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://waterservices.usgs.gov/nwis/iv/?format=json&amp;amp;sites=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;site_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;parameterCd=00060,00065&amp;amp;siteStatus=all&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_flow_and_height_from_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dashboard_v1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
  Synchronous view that loads data one at a time
  &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
  &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="n"&gt;river_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;site_id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SITES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
      &lt;span class="n"&gt;river_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;SITES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;site_id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nf"&gt;get_river_flow_and_height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;site_id&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rivers/dashboard.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;river_data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;river_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;load_time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fnextlinklabs.com%2Fstatic%2Fcb8ecefa3b6fcabb4313b17c16cfb5a1%2F52ae2%2Friver-dashboard-v1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fnextlinklabs.com%2Fstatic%2Fcb8ecefa3b6fcabb4313b17c16cfb5a1%2F52ae2%2Friver-dashboard-v1.png" alt="Version 1 Results Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The data loads and takes almost four seconds. For the purposes of this post, that’ll be our baseline. Let’s see if we can improve that situation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 2: A Concurrent View Loading Some Data Simultaneously
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dashboard_v2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
  Concurrent view that loads some data simultaneously
  &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
  &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="n"&gt;river_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

  &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;site_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SITES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_river_flow_and_height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SITES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())):&lt;/span&gt;
          &lt;span class="n"&gt;river_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;SITES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;site_id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rivers/dashboard.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;river_data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;river_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;load_time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fnextlinklabs.com%2Fstatic%2F5ebd36374501096265fc54795b41ad3c%2F0e677%2Friver-dashboard-v2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fnextlinklabs.com%2Fstatic%2F5ebd36374501096265fc54795b41ad3c%2F0e677%2Friver-dashboard-v2.png" alt="Version 2 Results Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we’re down to roughly 1.5 seconds and that’s a big improvement. Let’s see what happens when we leverage asynchronous views.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 3: The Asynchronous Method
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_river_flow_and_height_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;site_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
  Asynchronous method to get river data from USGS
  &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://waterservices.usgs.gov/nwis/iv/?format=json&amp;amp;sites=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;site_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;parameterCd=00060,00065&amp;amp;siteStatus=all&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_flow_and_height_from_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dashboard_v3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
  Asynchronous view that loads data using asyncio
  &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
  &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="n"&gt;river_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

  &lt;span class="n"&gt;datas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;get_river_flow_and_height_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;site_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;site_id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SITES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;site_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SITES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;datas&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="n"&gt;river_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;SITES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;site_id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rivers/dashboard.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;river_data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;river_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;load_time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fnextlinklabs.com%2Fstatic%2F18078f4a9265bc0718ef195c24dd1f48%2F7a2f6%2Friver-dashboard-v3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fnextlinklabs.com%2Fstatic%2F18078f4a9265bc0718ef195c24dd1f48%2F7a2f6%2Friver-dashboard-v3.png" alt="Version 3 Results Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wow, we got results back in just under a second. That’s roughly a three second improvement on the original method.&lt;/p&gt;

&lt;p&gt;This example shows pretty clearly how asynchronous views can be leveraged to drastically improve performance.&lt;/p&gt;

&lt;p&gt;View the full project on GitLab: &lt;a href="https://gitlab.com/nextlink/example-django-async-rivers" rel="noopener noreferrer"&gt;https://gitlab.com/nextlink/example-django-async-rivers&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post originally appeared on our &lt;a href="https://nextlinklabs.com/" rel="noopener noreferrer"&gt;blog at NextLink Labs&lt;/a&gt; where, among other things, we write about &lt;a href="https://nextlinklabs.com/services/devops-consulting-services" rel="noopener noreferrer"&gt;devops consulting&lt;/a&gt; &lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>api</category>
      <category>performance</category>
    </item>
    <item>
      <title>Efficient Iteration of Big Data in Django</title>
      <dc:creator>Kyle Johnson</dc:creator>
      <pubDate>Thu, 06 May 2021 19:24:27 +0000</pubDate>
      <link>https://dev.to/kjpctech/efficient-iteration-of-big-data-in-django-354m</link>
      <guid>https://dev.to/kjpctech/efficient-iteration-of-big-data-in-django-354m</guid>
      <description>&lt;h2&gt;
  
  
  Running out of memory is not fun.
&lt;/h2&gt;

&lt;p&gt;Unfortunately, when working with larger datasets its bound to happen at some point. &lt;/p&gt;

&lt;p&gt;For example, I tried to run a &lt;a href="https://nextlinklabs.com/insights/django-upgrade-guide-for-major-and-minor-releases" rel="noopener noreferrer"&gt;Django&lt;/a&gt; management command that updated a value on a model with a large amount of rows in the database table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python manage.py my_update_command
Killed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was inside of Kubernetes which killed the process when it exceeded its memory limit. In a more traditional environment, you can completely freeze up the server if it runs out of memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context
&lt;/h3&gt;

&lt;p&gt;You're probably wondering why I'm trying to run a management command like this in the first place. When working with large datasets, its best to avoid anything that is &lt;code&gt;O(n)&lt;/code&gt; or worse. In this case, I had a JSONField with a bunch of data. I also had an IntegerField on the model that stored a calculation based on some of the data in the JSONField. &lt;/p&gt;

&lt;p&gt;Of course, requirements change and the calculation I had been using needed to use different values from the JSONField. This also needed to happen for all the existing data in the database (the large amount of rows). Luckily I had everything stored in the JSONField and making this change was as simple as running the management command and patiently waiting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.core.management import BaseCommand

from ...models import SomeModel
from ...utils import some_calculation


class Command(BaseCommand):
    help = "Updates SomeModel.field based on some_calculation"

    def handle(self, *args, **options):
        self.stdout.write("Starting")

        try:
            queryset = SomeModel.objects.all()

            for obj in queryset:
                obj.field = some_calculation(obj)
                obj.save(update_fields=["field"])
        except KeyboardInterrupt:
            self.stdout.write("KeyboardInterrupt")

        self.stdout.write("Done")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Killed, now what?
&lt;/h3&gt;

&lt;p&gt;Naturally, it wasn't that simple. Method 1 was using enough memory to have &lt;a href="https://nextlinklabs.com/insights/kubernetes-ci-cd-gitlab-with-helm" rel="noopener noreferrer"&gt;Kubernetes&lt;/a&gt; stop it. I tried a few different things here including moving the code into a data migration and running multiple asynchronous tasks. I had difficulties getting these approaches working and struggled to monitor progress. &lt;/p&gt;

&lt;p&gt;Really, I just wanted a simple, memory-efficient management command to iterate through the data and update it.&lt;/p&gt;

&lt;h3&gt;
  
  
  QuerySet.iterator
&lt;/h3&gt;

&lt;p&gt;Django's built-in solution to iterating though a larger QuerySet is the &lt;a href="https://docs.djangoproject.com/en/dev/ref/models/querysets/#iterator" rel="noopener noreferrer"&gt;QuerySet.iterator method&lt;/a&gt;. This helps immensely and is probably good enough in most cases. &lt;/p&gt;

&lt;p&gt;However, method 2 was still getting killed in my case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# simplified command using QuerySet.iterator
class Command(BaseCommand):
    def handle(self, *args, **options):
        queryset = SomeModel.objects.all().iterator(chunk_size=1000)

        for obj in queryset:
            obj.field = some_calculation(obj)
            obj.save(update_fields=["field"])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Behold the Paginator
&lt;/h3&gt;

&lt;p&gt;I needed to iterate through the QuerySet by using smaller chunks in a more memory-efficient manner then the iterator method. I started to roll out my own solution before I realized that this sounded very familiar. &lt;a href="https://docs.djangoproject.com/en/dev/topics/pagination/" rel="noopener noreferrer"&gt;Django has pagination support built-in&lt;/a&gt; which is exactly what I was about to implement. I ended up using the &lt;a href="https://docs.djangoproject.com/en/dev/ref/paginator/#django.core.paginator.Paginator" rel="noopener noreferrer"&gt;Django Paginator&lt;/a&gt; to iterate through the QuerySet in chunks. &lt;/p&gt;

&lt;p&gt;Method 4 works great.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# simplified command using Paginator
class Command(BaseCommand):
    def handle(self, *args, **options):
        queryset = SomeModel.objects.all()

        paginator = Paginator(queryset, 1000)

        for page_number in paginator.page_range:
            page = paginator.page(page_number)

            for obj in page.object_list:
                obj.field = some_calculation(obj)
                obj.save(update_fields=["field"])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Memory Usage
&lt;/h3&gt;

&lt;p&gt;At this point there are the three methods described above, plus another two I added that use &lt;a href="https://docs.djangoproject.com/en/dev/ref/models/querysets/#bulk-update" rel="noopener noreferrer"&gt;QuerySet.bulk_update&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Regular QuerySet&lt;/li&gt;
&lt;li&gt;QuerySet.iterator&lt;/li&gt;
&lt;li&gt;QuerySet.iterator and QuerySet.bulk_update&lt;/li&gt;
&lt;li&gt;Paginator&lt;/li&gt;
&lt;li&gt;Paginator and QuerySet.bulk_update&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I ran comparisons on these approaches with 50,000 items in the database. Here is memory usage for all five methods with three runs each:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fnextlinklabs.com%2Fstatic%2F0daa46bdb5fa4af1dfe8e35b343f0b3f%2Fc81eb%2Fcomparison-all.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fnextlinklabs.com%2Fstatic%2F0daa46bdb5fa4af1dfe8e35b343f0b3f%2Fc81eb%2Fcomparison-all.png" alt="Memory Comparison with All Methods"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The plot clearly shows that method 1 is not a good choice since the entire QuerySet is loaded into memory before it can be used. Method 3 is also showing a steady increase in memory (note: it appears there is a memory leak here that I was unable to resolve). Zooming in on methods 2, 4 and 5 it becomes more clear that methods 4 and 5 are the winners:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fnextlinklabs.com%2Fstatic%2F8dbca00459062ffdf7dcd0b8be08cee0%2Fc81eb%2Fcomparison-performant.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fnextlinklabs.com%2Fstatic%2F8dbca00459062ffdf7dcd0b8be08cee0%2Fc81eb%2Fcomparison-performant.png" alt="Memory Comparison with Methods 2, 4 and 5"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I don't claim that the Paginator solution is always the best or even the best for my problem. More importantly it solved my problem and gave me the opportunity to dive into the memory differences between these approaches described above. If you have a similar problem, I recommend diving in and seeing how the comparison pans out for the specific problem.&lt;/p&gt;

&lt;p&gt;A shortened version of the method 5 management command is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# simplified command using Paginator and QuerySet.bulk_update
class Command(BaseCommand):
    def handle(self, *args, **options):
        queryset = SomeModel.objects.all()

        paginator = Paginator(queryset, 1000)

        for page_number in paginator.page_range:
            page = paginator.page(page_number)
            updates = []

            for obj in page.object_list:
                obj.field = some_calculation(obj)
                updates.append(obj)

            SomeModel.objects.bulk_update(updates, ["field"])

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This article was originally posted on our &lt;a href="https://nextlinklabs.com/insights/django-big-data-iteration" rel="noopener noreferrer"&gt;blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>django</category>
      <category>bigdata</category>
      <category>python</category>
    </item>
  </channel>
</rss>
