One place for hosting & domains

      September 2021

      How To Use Indexes in MongoDB


      The author selected the Open Internet/Free Speech Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      MongoDB is a document-oriented database management system that allows you to store large amounts of data in documents that can vary in terms of size and structure. MongoDB features a powerful query mechanism that allows you to filter documents based on specific criteria. As a MongoDB collection grows, though, searching for documents can become like searching for a needle in a haystack.

      The flexibility that MongoDB offers with regard to queries can make it difficult for the database engine to predict what kinds of queries will be used most frequently; it must be ready to find documents regardless of the size of the collection. Because of this, the amount of data held in a collection directly impacts search performance: the bigger the data set, the more difficult it is for MongoDB to find the documents matching the query.

      Indexes are one of the most essential tools the database administrator can use to consciously aid the database engine and improve its performance. In this tutorial, you’ll learn what indexes are, how to create them, and how to check how they’re used when the database performs queries.

      Prerequisites

      To follow this tutorial, you will need:

      Note: The linked tutorials on how to configure your server, install, and then secure MongoDB installation refer to Ubuntu 20.04. This tutorial concentrates on MongoDB itself, not the underlying operating system. It will generally work with any MongoDB installation regardless of the operating system as long as authentication has been enabled.

      Understanding Indexes

      Typically, when you query a MongoDB database to retrieve documents that match a particular condition — such as mountain peaks with a height greater than 8000 meters — the database must perform a collection scan to find them. This means that it will retrieve every document from the collection to verify whether they match the condition. If a document does match the condition, it will be added to the list of returned documents. If a document doesn’t match the specified condition, MongoDB will move on to scanning the next document until it has finished scanning the entire collection.

      This mechanism works well for many use cases, but it can become noticeably slow when the collection grows larger. This becomes more pronounced if the documents stored in the collection are complex; if a collection’s documents are more than just a few fields, it can be an expensive operation to read and then analyze their contents.

      Indexes are special data structures that store only a small subset of the data held in a collection’s documents separately from the documents themselves. In MongoDB, they are implemented in such a way that the database can quickly and efficiently traverse them when searching for values.

      To help understand indexes, imagine a database collection storing products in an online store. Each product is represented by a document containing images, detailed descriptions, category relationships, and many other fields. The application frequently runs a query against this collection to check which products are in stock.

      Without any indexes, MongoDB would need to retrieve every product from the collection and check the stock information in the document structure. With an index, though, MongoDB will maintain a separate, smaller list containing only pointers to products in stock. MongoDB can then use this structure to find which products are in stock much more quickly.

      In the following steps, you’ll prepare a sample database and use it to create indexes of different types. You’ll learn how to verify if the indexes are used when doing a query. Finally, you’ll learn how to list previously-defined indexes and remove them, if desired.

      Step 1 — Preparing the Sample Database

      In order to learn how indexes work and how to create them, this step outlines how to open the MongoDB shell to connect to your locally-installed MongoDB instance. It also explains how to create a sample collection and insert a few sample documents into it. This guide will use this sample data to illustrate different types of indexes that MongoDB can use to improve the query performance.

      To create this sample collection, connect to the MongoDB shell as your administrative user. This tutorial follows the conventions of the prerequisite MongoDB security tutorial and assumes the name of this administrative user is AdminSammy and its authentication database is admin. Be sure to change these details in the following command to reflect your own setup, if different:

      • mongo -u AdminSammy -p --authenticationDatabase admin

      Enter the password you set during installation to gain access to the shell. After providing the password, your prompt will change to a greater-than sign (>).

      Note: On a fresh connection, the MongoDB shell will automatically connect to the test database by default. You can safely use this database to experiment with MongoDB and the MongoDB shell.

      Alternatively, you could also switch to another database to run all of the example commands given in this tutorial. To switch to another database, run the use command followed by the name of your database:

      To illustrate how indexes work, we’ll need a collection of documents with multiple fields of different types. We’ll be using the sample collection of the five highest mountains in the world. The following is an example document representing Mount Everest:

      The Everest document

      {
          "name": "Everest",
          "height": 8848,
          "location": ["Nepal", "China"],
          "ascents": {
              "first": {
                  "year": 1953,
              },
              "first_winter": {
                  "year": 1980,
              },
              "total": 5656,
          }
      }
      

      This document contains the following information:

      • name: the peak’s name.
      • height: the peak’s elevation, in meters.
      • location: the countries in which the mountain is located. This field stores values as an array to allow for mountains located in more than one country.
      • ascents: this field’s value is another document. When one document is stored within another document like this, it’s known as an embedded or nested document. Each ascents document describes successful ascents of the given mountain. Specifically, each ascents document contains a total field that lists the total number of successful ascents of each given peak. Additionally, each of these nested documents contain two fields whose values are also nested documents:.
        • first: this field’s value is a nested document that contains one field, year, which describes the year of the first overall successful ascent.
        • first_winter: this field’s value is a nested document that also contains a year field, the value of which represents the year of the first successful winter ascent of the given mountain.

      Run the following insertMany() method in the MongoDB shell to simultaneously create a collection named peaks and insert five sample documents into it. These documents describe the five tallest mountain peaks in the world:

      • db.peaks.insertMany([
      • {
      • "name": "Everest",
      • "height": 8848,
      • "location": ["Nepal", "China"],
      • "ascents": {
      • "first": {
      • "year": 1953
      • },
      • "first_winter": {
      • "year": 1980
      • },
      • "total": 5656
      • }
      • },
      • {
      • "name": "K2",
      • "height": 8611,
      • "location": ["Pakistan", "China"],
      • "ascents": {
      • "first": {
      • "year": 1954
      • },
      • "first_winter": {
      • "year": 1921
      • },
      • "total": 306
      • }
      • },
      • {
      • "name": "Kangchenjunga",
      • "height": 8586,
      • "location": ["Nepal", "India"],
      • "ascents": {
      • "first": {
      • "year": 1955
      • },
      • "first_winter": {
      • "year": 1986
      • },
      • "total": 283
      • }
      • },
      • {
      • "name": "Lhotse",
      • "height": 8516,
      • "location": ["Nepal", "China"],
      • "ascents": {
      • "first": {
      • "year": 1956
      • },
      • "first_winter": {
      • "year": 1988
      • },
      • "total": 461
      • }
      • },
      • {
      • "name": "Makalu",
      • "height": 8485,
      • "location": ["China", "Nepal"],
      • "ascents": {
      • "first": {
      • "year": 1955
      • },
      • "first_winter": {
      • "year": 2009
      • },
      • "total": 361
      • }
      • }
      • ])

      The output will contain a list of object identifiers assigned to the newly inserted objects.

      Output

      { "acknowledged" : true, "insertedIds" : [ ObjectId("61212a8300c8304536a86b2f"), ObjectId("61212a8300c8304536a86b30"), ObjectId("61212a8300c8304536a86b31"), ObjectId("61212a8300c8304536a86b32"), ObjectId("61212a8300c8304536a86b33") ] }

      You can verify that the documents were properly inserted by running the find() method with no arguments, which will retrieve all documents:

      Output

      { "_id" : ObjectId("61212a8300c8304536a86b2f"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } ...

      Please note that this example collection is not big enough to directly illustrate the performance impact of indexes or lack thereof. However, this guide will outline how MongoDB uses indexes to limit the amount of traversed documents by higlighting query details as reported by the database engine.

      With the sample data in place, you can continue on to the next step to learn how to create an index based on a single field.

      Step 2 — Creating a Single Field Index and Evaluating Index Usage

      This step explains how to create a single field index in order to speed up document queries that filter data using that field as part of the filtering condition. It also outlines how you can verify whether MongoDB used an index to boost the query performance or resorted to a full collection scan instead.

      To begin, run the following query. Normally, the query document { "height": { $gt: 8700 } } would cause this query to retrieve any documents that describe a mountain peak with a height value greater than 8700. However, this operation includes the explain(executionStats) method, which will cause the query to instead return information about how the query is performed. Because you haven’t yet created any indexes, this will provide you with a benchmark which you can use to compare against the performance of queries that do use indexes:

      • db.peaks.find(
      • { "height": { $gt: 8700 } }
      • ).explain("executionStats")

      This operation returns a lot of information. The following example output removes a number of lines that aren’t important for the purposes of this tutorial:

      Output

      { "queryPlanner" : { . . . "winningPlan" : { "stage" : "COLLSCAN", . . . }, }, . . . "executionStats" : { . . . "nReturned" : 1, "executionTimeMillis" : 0, "totalKeysExamined" : 0, "totalDocsExamined" : 5, . . . }, . . . }

      The following fields returned in this output are particularly relevant to understanding how indexes work:

      • winningPlan: This document within the queryPlanner section describes how MongoDB decided to execute the query. Depending on the query type, the detailed structure of the winningPlan may differ, but here the key thing to note is COLLSCAN. The presence of this value means that MongoDB needed to go through the full collection without any aids to find the requested documents.
      • nReturned: This value tells you how many documents were returned by a given query. Here, just a single mountain peak matches the query.
      • executionTimeMillis: This value represents the execution time. With such a small collection, its importance is negligible. However, when analyzing the performance of queries against larger or more complex collections, it is an important metric to keep in mind.
      • totalKeysExamined: This tells you how many index entries MongoDB checked to find the requested documents. Because the collection scan was used and you haven’t created any indexes yet, the value is 0.
      • totalDocsExamined: This value indicates how many documents MongoDB had to read from the collection. Because MongoDB performed a collection scan, its value is 5, the total count of all documents in the collection. The larger the collection, the bigger the value in this field when indexes are not used.

      Notice the discrepancy between the total examined documents and returned documents counts: MongoDB had to inspect 5 documents in order to return one.

      This tutorial will reference these values in later sections to analyze how indexes affect the way that queries are executed.

      To that end, create an index on the height field in the peaks collection using the createIndex() method. This method accepts a JSON document describing the index you want to create. This example will create a single field index, meaning that the document contains a single key (height in this example) for the field we want to use. This key accepts either 1 or -1 as a value. These values denote the index’s sorting order, with 1 indicating ascending order and -1 indicating descending:

      • db.peaks.createIndex( { "height": 1 } )

      Note: With single field indexes, the ordering is not important, since the index structure can be traversed in both directions efficiently. Choosing the order for index fields becomes more important with compound indexes based on multiple fields, as described in Step 4.

      MongoDB returns a confirmation indicating how many indexes have been defined on the collection now, and how that differs from the previous state.

      Output

      { "createdCollectionAutomatically" : false, "numIndexesBefore" : 1, "numIndexesAfter" : 2, "ok" : 1 }

      Now try executing the same query you ran previously. This time, though, the information returned by the explain("executionStats") method will differ because there is an index in place:

      • db.peaks.find(
      • { "height": { $gt: 8700 } }
      • ).explain("executionStats")

      Output

      { "queryPlanner" : { . . . "winningPlan" : { . . . "inputStage" : { "stage" : "IXSCAN", . . . "indexName" : "height_1", . . . } }, . . . }, "executionStats" : { . . . "nReturned" : 1, "executionTimeMillis" : 0, "totalKeysExamined" : 1, "totalDocsExamined" : 1, . . . }, . . . }

      Notice that winningPlan no longer shows COLLSCAN. Instead, IXSCAN is present, indicating that the index was used as part of the query execution. MongoDB also informs you which index was used through the indexName value. By default, MongoDB constructs index names from the field names to which the index is bound and the ordering applied. From { "height": 1 }, MongoDB automatically generated the name height_1.

      The most important change is in the executionStats section. Once again, this query only returned a single document, as denoted by nReturned. However, this time the totalDocsExamined is only 1. This means that the database retrieved just one document from the collection to satisfy the query. The totalKeysExamined reveals that the index was checked just one time because it provided enough information to compile the result.

      By creating this index, you’ve reduced the number of documents MongoDB had to inspect from 5 to 1, a five-fold reduction. If the peaks collection contained thousands of entries, the impact of using an index would be even more apparent.

      Step 3 — Creating Unique Indexes

      In MongoDB, it’s impossible to insert two documents into the collection if they both have the same _id values. This is because the database automatically maintains a single-field index on the _id field that, in addition to helping speed up document lookups, ensures the uniqueness of the _id field value. This step explains how you can create indexes to ensure the values of a given field will be unique for every document in a collection.

      To illustrate, run the following createIndex() method. This command’s syntax is similar to the one used in the previous step except, this time, a second parameter is passed to createIndex() with additional settings for the index. The { "unique": true } indicates that the created index will ensure that the values of the specified field (name) can’t repeat:

      • db.peaks.createIndex( { "name": 1 }, { "unique": true } )

      Once again, MongoDB will confirm that the index was created successfully:

      Output

      { "createdCollectionAutomatically" : false, "numIndexesBefore" : 2, "numIndexesAfter" : 3, "ok" : 1 }

      Next, check whether the index serves its primary purpose and runs any queries against mountain names faster by avoiding collection scans. To do so, run the following equality query with the explain("executionStats") method:

      • db.peaks.find(
      • { "name": "Everest" }
      • ).explain("executionStats")

      The returned query plan uses the IXSCAN strategy with the newly-created index, just like with mountain height query from the previous step:

      Output

      { "queryPlanner" : { . . . "winningPlan" : { . . . "inputStage" : { "stage" : "IXSCAN", . . . "indexName" : "name_1", . . . } }, . . . }, . . . }

      Next check whether you’re able to add a second document representing Mt. Everest to the collection now that the index is in place. Do so by running the following insertOne() method:

      • db.peaks.insertOne({
      • "name": "Everest",
      • "height": 9200,
      • "location": ["India"],
      • "ascents": {
      • "first": {
      • "year": 2020
      • },
      • "first_winter": {
      • "year": 2021
      • },
      • "total": 2
      • }
      • })

      MongoDB will not create the document and will instead return an error message:

      Output

      WriteError({ "index" : 0, "code" : 11000, "errmsg" : "E11000 duplicate key error collection: test.peaks index: name_1 dup key: { name: "Everest" }", "op" : { . . .

      This duplicatye key error message refers to the name_1 index, indicating that it’s enforcing a uniqueness constraint on this field.

      With that, you’ve learned how to create a unique index to prevent a given field from containing duplicate values. Continue reading to learn how to use indexes with embedded documents.

      Step 4 — Creating an Index on an Embedded Field

      Whenever you query a collection using a field within a nested document that doesn’t have an index, MongoDB not only has to retrieve all documents from the collection, but it must also traverse each nested document.

      As an example, run the following query. This will return any documents whose total — a field nested within the ascents document found in each document in the peaks collection — is greater than 300 and sorts the results in descending order:

      • db.peaks.find(
      • { "ascents.total": { $gt: 300 } }
      • ).sort({ "ascents.total": -1 })

      This query will return four peaks from the collection, with Mt. Everest being the peak with the most ascents, followed by Lhotse, Makalu, and K2:

      Output

      { "_id" : ObjectId("61212a8300c8304536a86b2f"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }
      { "_id" : ObjectId("61212a8300c8304536a86b32"), "name" : "Lhotse", "height" : 8516, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1956 }, "first_winter" : { "year" : 1988 }, "total" : 461 } }
      { "_id" : ObjectId("61212a8300c8304536a86b33"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }
      { "_id" : ObjectId("61212a8300c8304536a86b30"), "name" : "K2", "height" : 8611, "location" : [ "Pakistan", "China" ], "ascents" : { "first" : { "year" : 1954 }, "first_winter" : { "year" : 1921 }, "total" : 306 } }
      

      Now run the same query, but include the explain("executionStats") method used previously:

      • db.peaks.find(
      • { "ascents.total": { $gt: 300 } }
      • ).sort({ "ascents.total": -1 }).explain("executionStats")

      As the COLLSCAN value in this section of the output indicates, MongoDB resorted to a full collection scan and traversed all the documents from the peaks collection to compare them against the query conditions:

      Output

      { . . . "winningPlan" : { "stage" : "COLLSCAN", . . . }, . . . }

      Because this collection only has five entries, the lack of an index didn’t significantly affect performance and this query executed immediately. However, the more complex the documents stored in the database, the greater the performance impact queries can have. This step outlines how to create single-field indexes on fields inside embedded documents to help mitigate this issue.

      To help MongoDB execute this query, let’s create an index on the total field within the ascents document. Because the total field is nested within ascents, it’s not possible to specify total as the field name when creating this index. Instead, MongoDB provides a dot notation to access fields in nested documents. To refer to total field inside ascents nested document, you can use the ascents.total notation, like this:

      • db.peaks.createIndex( { "ascents.total": 1 } )

      MongoDB will reply with a success message letting you know that you now have four indexes defined.

      {
              "createdCollectionAutomatically" : false,
              "numIndexesBefore" : 3,
              "numIndexesAfter" : 4,
              "ok" : 1
      }
      

      Note: In this tutorial, we add additional indexes from step to step to illustrate how different types of indexes can be used. However, it is important to be aware that adding too many indexes can be as bad for performance as having too few.

      For every index in the database, MongoDB must keep each properly updated whenever a new document is inserted to the collection or any are changed. The performance penalty of having many indexes can counter the benefits they provide through increasing query speed. Make sure to add indexes only for fields that are queried often or have the most impact on performance.

      Run the previous query once again to check whether the index helped MongoDB avoid performing a full collection scan:

      • db.peaks.find(
      • { "ascents.total": { $gt: 300 } }
      • ).sort({ "ascents.total": -1 }).explain("executionStats")

      Output

      { "queryPlanner" : { . . . "winningPlan" : { . . . "inputStage" : { "stage" : "IXSCAN", . . . "indexName" : "ascents.total_-1", . . . } }, . . . }, "executionStats" : { . . . "nReturned" : 4, "executionTimeMillis" : 0, "totalKeysExamined" : 4, "totalDocsExamined" : 4, . . . "direction" : "backward", . . . }, . . . }

      Notice that now IXSCAN is used against the newly created ascents.total_-1 index, and only four documents have been examined. This is the same number of documents returned and examined in the index, so no additional documents have been retrieved to complete the query.

      direction, another field in the executionStats section, indicates which direction MongoDB decided to traverse the index. Because the index was created as ascending, using the { "ascents.total": 1 } syntax, and the query requested mountain peaks sorted in descending order, the database engine decided to go backwards. When retrieving documents in a particular order based on a field that’s part of an index, MongoDB will use the index to provide final ordering without the need to further sort documents after retrieving them in full.

      Step 5 — Creating a Compound Field Index

      The examples so far in this guide are helpful for understanding the benefits of using indexes, but document filtering queries used in real world applications are rarely this simple. This step explains how MongoDB uses indexes when executing queries on more than one field and how to use compound field indexes to target such queries specifically.

      Recall from Step 2 when you created a single field index on the height field in order to more efficiently query the peaks collection to find the highest mountain peaks. With this index in place, let’s analyze how MongoDB will perform a similar but slightly more complex query. Try finding mountains with a height of less than 8600 meters whose first winter ascent occurred after the year 1990:

      • db.peaks.find(
      • {
      • "ascents.first_winter.year": { $gt: 1990 },
      • "height": { $lt: 8600 }
      • }
      • ).sort({ "height": -1 })

      Only a single mountain — Makalu — satisfies both of these conditions:

      Output

      { "_id" : ObjectId("61212a8300c8304536a86b33"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }

      Now add the explaion("executionStats") method to find how MongoDB performed this query:

      • db.peaks.find(
      • {
      • "ascents.first_winter.year": { $gt: 1990 },
      • "height": { $lt: 8600 }
      • }
      • ).sort({ "height": -1 }).explain("executionStats")

      Even though there is no index that might affect the first winter ascent date, MongoDB used a previously-created index instead of doing a full collection scan:

      Output

      { "queryPlanner" : { . . . "winningPlan" : { "stage" : "IXSCAN", . . . "indexName" : "height_1", . . . } }, . . . }, "executionStats" : { . . . "nReturned" : 1, "executionTimeMillis" : 0, "totalKeysExamined" : 3, "totalDocsExamined" : 3, . . . }, . . . }

      Notice that this time, different from previous index-backed query executions, the nReturned value denoting the number of returned documents is different than both totalKeysExamined and totalDocsExamined. MongoDB used the single field index on the height field to narrow down the results from 5 to 3, but then had to scan the remaining documents to check the first winter ascent date.

      If an index is only available for one part of a query, MongoDB will use it to narrow down the results first before doing a collection scan. It will traverse only the list of documents it initially filtered in order to satisfy the rest of the query.

      In many situations, this is entirely sufficient. If the most common queries examine a single indexed field and must only occasionally perform additional filtering, having a single field index is usually good enough. When queries against multiple fields are common, though, it might be beneficial to define an index spanning all these fields to make sure no additional scans must be performed.

      Imagine that you query the database for mountain peaks satisfying conditions related to their first winter ascent and height regularly enough that it becomes a performance concern and would benefit from having an index. To create an index based on both of these fields fields, run the following createIndex(0) method:

      • db.peaks.createIndex(
      • {
      • "ascents.first_winter.year": 1,
      • "height": -1
      • }
      • )

      Notice this operation’s syntax is similar to the single field index creation, but this time both fields are listed in the index definition object. The index is created as ascending regarding the peaks’ first winter ascents and descending with regards to their heights.

      MongoDB will acknowledge that the index was successfully created:

      Output

      { "createdCollectionAutomatically" : false, "numIndexesBefore" : 4, "numIndexesAfter" : 5, "ok" : 1 }

      With single field indexes, the database engine can freely traverse the index either forwards or backwards. However, with compound indexes this is not always the case. If a particular sorting order for a combination of fields is queried more often, it can further increase performance to include that order in the index definition. MongoDB will then satisfy the requested ordering using the index directly, rather than doing additional sorting on the list of returned documents.

      Run the previous query once again to test whether there was any change in how the query was performed:

      • db.peaks.find(
      • {
      • "ascents.first_winter.year": { $gt: 1990 },
      • "height": { $lt: 8600 }
      • }
      • ).sort({ "height": -1 }).explain("executionStats")

      This time the query again used an index scan, but the index is different. Now, the ascents.first_winter.year_1_height_-1 index that you just created is chosen over the previously-used height_1 index:

      Output

      { "queryPlanner" : { . . . "winningPlan" : { "stage" : "IXSCAN", . . . "indexName" : "ascents.first_winter.year_1_height_-1", . . . } }, . . . }, "executionStats" : { . . . "nReturned" : 1, "executionTimeMillis" : 0, "totalKeysExamined" : 1, "totalDocsExamined" : 1, . . . }, . . . }

      The important difference lies in executionStats. With the new index, a single document was examined directly from the index and then returned, as opposed to three documents requiring further document scans to narrow the results down. If this was a larger collection, the difference between the new compound index and using a single field index with further filtering would be even more pronounced.

      Now that you’ve learned how to create indexes that span more than one field, you can move on to learning about multi-key indexes and how they’re used.

      Step 6 — Creating a Multi-key Index

      In previous examples, the fields used in indexes had single values stored in them, like a height, a year, or a name. In these cases, MongoDB stored the field value directly as the index key, making the index quickly traversable. This step outlines how MongoDB behaves when the field used to create the index is a field storing multiple values, such as an array.

      To begin, try finding all the mountains in the collection that are located in Nepal:

      • db.peaks.find(
      • { "location": "Nepal" }
      • )

      Four peaks are returned:

      Output

      { "_id" : ObjectId("61212a8300c8304536a86b2f"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "_id" : ObjectId("61212a8300c8304536a86b31"), "name" : "Kangchenjunga", "height" : 8586, "location" : [ "Nepal", "India" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 1986 }, "total" : 283 } } { "_id" : ObjectId("61212a8300c8304536a86b32"), "name" : "Lhotse", "height" : 8516, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1956 }, "first_winter" : { "year" : 1988 }, "total" : 461 } } { "_id" : ObjectId("61212a8300c8304536a86b33"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }

      Notice that none of these peaks are only in Nepal. Each of these four peaks span more than one country as indicated by their location fields, all of which are an array of multiple values. What is more, these values can appear in different orders. For example, Lhotse is listed as being in [ "Nepal", "China" ], whereas Makalu is listed as being in [ "China", "Nepal" ].

      Because there is no index available spanning the location field, MongoDB currently does a full collection scan to execute that query. Let’s create a new index for the location field:

      • db.peaks.createIndex( { "location": 1 } )

      Notice that this syntax does not differ from any other single field index. MongoDB will return a success message, and the index is now available to use:

      Output

      { "createdCollectionAutomatically" : false, "numIndexesBefore" : 5, "numIndexesAfter" : 6, "ok" : 1 }

      Now that you’ve created an index for the location field, run the previous query again with the explain("executionStats") method to understand how it executes:

      • db.peaks.find(
      • { "location": "Nepal" }
      • ).explain("executionStats")

      The resulting output indicates that MongoDB used an index scan as the strategy, referring to the newly-created location_1 index:

      Output

      { "queryPlanner" : { . . . "winningPlan" : { . . . "inputStage" : { "stage" : "IXSCAN", . . . "indexName" : "location_1", "isMultiKey" : true, . . . } }, . . . }, "executionStats" : { . . . "nReturned" : 4, "executionTimeMillis" : 0, "totalKeysExamined" : 4, "totalDocsExamined" : 4, . . . } . . . }

      The number of returned documents matches the total number of examined index keys and examined documents. This means that the index was used as the sole source of information for the query. How was that possible if the field values are arrays of more than one value, and the query asked for mountains with one of the locations matching Nepal?

      Notice the isMultiKey property listed as true in the output. MongoDB automatically created a multi-key index for the location field. If you create an index for a field holding arrays, MongoDB automatically determines it needs to create a multi-key index and creates separate index entries for every element of these arrays.

      So, for a document that has a location field storing the array [ "China", "Nepal" ], two separate index entries appear for the same document, one for China and another for Nepal. This way, MongoDB can use the index efficiently even if the query requests a partial match against the array contents.

      Step 7 — Listing and Removing Indexes on a Collection

      In the previous steps, you’ve learned how to create different types of indexes. When the database grows or requirements change, it’s important to be able to know what indexes are defined and sometimes remove unwanted ones. Indexes that are no longer useful can have a negative impact on the database’s performance, since MongoDB must still maintain them any time you add or change data.

      To list all the indexes you’ve defined on the peaks collection throughout this tutorial, you can use the getIndexes() method:

      MongoDB will return the list of indexes, describing their nature and listing their names:

      Output

      [ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_" }, { "v" : 2, "key" : { "height" : 1 }, "name" : "height_1" }, { "v" : 2, "unique" : true, "key" : { "name" : 1 }, "name" : "name_1" }, { "v" : 2, "key" : { "ascents.total" : 1 }, "name" : "ascents.total_1" }, { "v" : 2, "key" : { "ascents.first_winter.year" : 1, "height" : -1 }, "name" : "ascents.first_winter.year_1_height_-1" }, { "v" : 2, "key" : { "location" : 1 }, "name" : "location_1" } ]

      Throughout this tutorial, you have defined 6 indexes altogether. For each one, the key property lists the index definition, matching the way the index was created before. For each index, the name property contains the name MongoDB generated automatically when creating the index.

      To delete an existing index, you can use either of these properties with the dropIndex() method. The following example will delete the height_1 index by using the definition of its contents:

      • db.peaks.dropIndex( { "height": 1 } )

      Since the { "height": 1 } matches the single field index on height named height_1, MongoDB will remove that index and reply with a success message indicating how many indexes there were prior to removing this one:

      Output

      { "nIndexesWas" : 6, "ok" : 1 }

      This way of specifying the index to remove can become unwieldy if the index definition is more complex, as can be the case with compound indexes. As an alternative, you can remove indexes using an index’s name. To remove the index created on the first winter ascent and height in Step 5 using its name, run the following operation:

      • db.peaks.dropIndex("ascents.first_winter.year_1_height_-1")

      Once again, MongoDB will remove the index and return a success message:

      Output

      { "nIndexesWas" : 5, "ok" : 1 }

      You can confirm that these two indexes have been indeed removed from the list of collection indexes by calling getIndexes() again:

      This time, only the four remaining indexes are listed:

      Output

      [ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_" }, { "v" : 2, "unique" : true, "key" : { "name" : 1 }, "name" : "name_1" }, { "v" : 2, "key" : { "ascents.total" : 1 }, "name" : "ascents.total_1" }, { "v" : 2, "key" : { "location" : 1 }, "name" : "location_1" } ]

      As a final note, be aware that it’s not possible to modify an existing index in MongoDB. If you ever need to change an index, you must first drop that index and create a new one.

      Conclusion

      By reading this article, you will have familiarized yourself with the concept of indexes — special data structures that can improve query performance by reducing the amount of data MongoDB must analyze during query execution. You have learned how to create single field, compound, and multi-key indexes, as well as how to check whether their presence affects query execution. You have also learned how to list existing indexes and delete unwanted ones.

      The tutorial described only a subset of indexing features provided by MongoDB to shape query performance in busy databases. We encourage you to study the official official MongoDB documentation to learn more about indexing and how it impacts performance in different scenarios.



      Source link

      How To Manage State in a Vue.js Application with Vuex


      The author selected Open Sourcing Mental Illness to receive a donation as part of the Write for DOnations program.

      Introduction

      Vuex is the first-party development state management library for Vue.js. It was created by Evan You and is currently maintained by the Vue.js Core Team. Like many other state management libraries, Vuex follows the principle that Redux has popularized over the past years: Data flows in one direction, with actions and mutations modifying data in a single source of truth called the store.

      A Vuex store is a collection of different methods and data. Some of these methods, such as actions, can fetch and process data before it is sent to a mutation. A mutation is a method that mutates or updates the store property with the value provided. Getters are methods that can modify or combine data to create a new state property. These getters are read-only and do not mutate data. These are similar to computed properties in a Vue.js component. The last component to Vuex is the state, or the dataset that acts as your single source of truth.

      In this tutorial, you will create an application that renders a list of cards with airport information in them. When clicked, these cards will execute the Vuex workflow to add the selected airport to a list of favorites. By running through this example, you will make actions and mutations to manage state and getters to retrieve computed data.

      Prerequisites

      Step 1 — Setting Up the Example Application

      In order to help visualize how state is managed with Vuex, set up a project with some data to display in the view. You will use this project and throughout the tutorial.

      Once the favorite-airports project is created as described in the Prerequisites section, create a directory to hold all of your local data for this project. Open your terminal and run the following commands in the project root directory (favorite-airports):

      • mkdir src/data
      • touch src/data/airports.js

      This will create the data directory and an empty airports.js file inside it.

      In your text editor of choice, open the newly created airports.js file and add in the following:

      favorite-airports/src/data/airports.js

      export default [
        {
          name: 'Cincinnati/Northern Kentucky International Airport',
          abbreviation: 'CVG',
          city: 'Hebron',
          state: 'KY'
        },
        {
          name: 'Seattle-Tacoma International Airport',
          abbreviation: 'SEA',
          city: 'Seattle',
          state: 'WA',
        },
        {
          name: 'Minneapolis-Saint Paul International Airport',
          abbreviation: 'MSP',
          city: 'Bloomington',
          state: 'MN',
        },
        {
          name: 'Louis Armstrong New Orleans International Airport',
          abbreviation: 'MSY',
          city: 'New Orleans',
          state: 'LA',
        },
        {
          name: `Chicago O'hare International Airport`,
          abbreviation: 'ORD',
          city: 'Chicago',
          state: 'IL',
        },
        {
          name: `Miami International Airport`,
          abbreviation: 'MIA',
          city: 'Miami',
          state: 'FL',
        }
      ]
      

      This is an array of objects consisting of a few airports in the United States. In this application, you are going to iterate through this data to generate cards consisting of the name, abbreviation, city, and state properties. When the user clicks on a card, you will execute a dispatch method, which will add that airport to your Vuex state as a favorite airport.

      Save data/airports.js and return to the terminal.

      When you’ve completed that step, create a single-file component (SFC) with the name AirportCard.vue. This file will live in the components directory of your project. This component will contain all the styles and logic for the airport card. In your terminal, create the .vue file using the touch command:

      • touch src/components/AirportCard.vue

      Open AirportCard.vue in your text editor and add the following:

      favorite-airports/src/components/AirportCard.vue

      <template>
        <div class="airport">
          <p>{{ airport.abbreviation }}</p>
          <p>{{ airport.name }}</p>
          <p>{{ airport.city }}, {{ airport.state }}</p>
        </div>
      </template>
      
      <script>
      export default {
        props: {
          airport: {
            type: Object,
            required: true
          }
        }
      }
      </script>
      
      <style scoped>
      .airport {
        border: 3px solid;
        border-radius: .5rem;
        padding: 1rem;
        margin-bottom: 1rem;
      }
      
      .airport p:first-child {
        font-weight: bold;
        font-size: 2.5rem;
        margin: 1rem 0;
      }
      
      .airport p:last-child {
        font-style: italic;
        font-size: .8rem;
      }
      </style>
      

      You may notice that there is some CSS included in this code snippet. In the AirportCard.vue component, the wrapper <div> contains the class of airport. This CSS adds some styling to the generated HTML by adding borders to give each airport the appearance of a “card”. The :first-child and :last-child are pseudo selectors that apply different styling to the first and last p tags in the HTML inside the div with the class of airport. In addition to that, you may also notice that this component contains a prop, which in Vue.js is a way to pass data down from a parent component to a child component.

      Save and exit from the file.

      Before wrapping up the setup, replace the existing App.vue component with the following code:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" />
          </div>
        </div>
      </template>
      
      <script>
      import { ref } from 'vue'
      import allAirports from '@/data/airports.js'
      import AirportCard from '@/components/AirportCard.vue'
      
      export default {
        components: {
          AirportCard
        },
        setup() {
          const airports = ref(allAirports)
          return { airports }
        }
      }
      </script>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      
      .wrapper {
        display: grid;
        grid-template-columns: 1fr 1fr 1fr;
        grid-column-gap: 1rem;
        max-width: 960px;
        margin: 0 auto;
      }
      
      p,
      h3 {
        grid-column: span 3;
      }
      </style>
      

      This code contains a v-for loop that iterates through the airports.js data and renders a series of AirportCards.vue components with airport data passed in via the prop :airport. Save this code and return to the command line.

      With the project set up, run the local development server using the npm run serve command in your terminal:

      This will start a server on your localhost, usually at port 8080. Open your web browser of choice and visit localhost:8080to see the following:

      A list of cards rendered in Vue using the v-for directive.

      Now that your example application is set up, in the next step you are going to install the Vuex library and create a store. This store is a collection of a number of different Vuex items including: state, mutations, actions, and getters. To illustrate this, you will be executing dispatch methods, which will add an airport to a favorites section of your app.

      Step 2 — Installing Vuex

      When working on web-based applications, you will often work with state. State is a collection of data at a given time. This state can be changed with user interactions via dispatch and commit methods. When the user modifies data, a dispatch event is executed, which passes data to a mutation and updates the state object.

      There are a few ways to approach updating state. Some developers skip actions and go straight to mutatations. However, for the sake of this tutorial, you will always execute an action that in turn calls a mutation. This way you can have multiple mutations inside of an action. The cardinal rule of Vuex is that mutations have one job and one job only: update the store. Actions can do a number of different things including combining data, fetching data, and running JavaScript logic.

      In addition to actions, there are also getters. A getter is a way to combine multiple state values into a single value. If you are familiar with computed properties in Vue.js, getters can be thought of as state-specific computed properties.

      With the Vuex terminology covered, start installing and integrating Vuex. Open your terminal and run the following command:

      • npm install vuex@next --save

      This command will install the version of Vuex that is the most compatible with Vue.js 3.x and saves it in your package.json file. Next, create a directory and an index file for your store. You will use the mkdir command to make a directory and touch to create a new file:

      • mkdir src/store
      • touch src/store/index.js

      Open your text editor and in your store/index.js file, initialize your Vuex store. To do this, you need to leverage the createStore function from Vuex:

      airport-favorites/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
      
      })
      

      You also export this, as you will later import it into your main.js file.

      At this point, you have your Vuex store set up, but the application is not yet aware of it or how to use it. To fully initialize the store, import it into your main.js file. In your text editor, open the src/main.js file.

      Immediately after createApp(App), chain the use method and pass into the store that you are importing, as shown in the following highlighted code:

      favorite-airports/src/main.js

      import { createApp } from 'vue'
      import App from './App.vue'
      import store from './store'
      
      createApp(App).use(store).mount('#app')
      

      Once you’ve chained the use method, save this file. The use method tells the Vue application which code to bundle together when the application is built. In this case, you are telling Vue to “use” or bundle the Vuex store.

      Before you move on to the next section, add a state value into your store and reference it in the App.vue file. Open your store/index.js file and add the following objects and values:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
      state: {
          firstName: 'John',
          lastName: 'Doe'
        },
      mutations: {
      
      },
      actions: {
      
      },
      getters: {
      
      }
      })
      

      These properties reflect the type of data the store holds: state for state (global data), mutations (commits that mutate data), actions (dispatches that call mutations), and getters (store computed properties).

      Save store/index.js, then open your App.vue file in your text editor and add the following:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <p>{{ $store.state.firstName }} {{ $store.state.lastName }}</p>
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" />
          </div>
        </div>
      </template>
      ...
      

      The $store in this case is the global store that you initialized in the main.js file. If you were to log this.$store into the console, you would see the store object. From there, the code accesses the property you want to display via dot notation.

      Save App.vue then open your web browser. Above the airport cards, you will see the first and last name that you saved to your Vuex store. These are the default values of firstName and lastName, respectively.

      Data from the Vuex store displayed in the view.

      In this step, you installed Vuex and created a Vuex store. You added some default store data and displayed it in the view with the $store object using dot notion. In the next step, you will be updating your Vuex store via actions and mutations, and you will get combined data with getters.

      Step 3 — Creating Actions, Mutations, and Getters

      In Step 2, you installed Vuex manually and integrated it into your project. In this step you still have the first and last name rendered in your browser, but you will create a Vuex getter to render the data as one string. As mentioned before, you can think of Vuex getters as computed properties for your Vuex store.

      To create a getter, open your src/store/index.js file in your text editor of choice. Once open, create a property in the getters object with a function as its value. The name of the property is how you will access the getter later.

      Add the following highlighted code:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
        state: {
          firstName: 'John',
          lastName: 'Doe'
        },
        ...
        getters: {
          fullName: function () {
      
          }
        }
      })
      

      In this case, you will use the function to combine the first and last names and store the resulting property as fullName. Inside the function, you will need to pass in the state object that is inside your Vuex store. From there, return a string with the first and last name interpolated:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
        state: {
          firstName: 'John',
          lastName: 'Doe'
        },
        ...
        getters: {
          fullName: function (state) {
            return `${state.firstName} ${state.lastName}`
          }
        }
      })
      

      You are using template literals here to put the firstName and lastName into one string.

      Save this file, then move back to App.vue. In this file, remove the first and last values and replace them with the getter:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <p>{{ $store.getters.fullName }}</p>
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" />
          </div>
        </div>
      </template>
      ...
      

      Once you make this change and save the file, your browser will hot reload. You will see the first and last name in your browser as before, but now you are leveraging the getter. If you change one of the names in your Vuex store, the getter will be updated automatically.

      Moving on from getters, there are actions. As mentioned in the last step, for the sake of this tutorial, you will always use an action rather than mutating the data directly.

      In this project, you will add an airport’s data to a “favorites” list when a user clicks on the card. You are going to first get the action and mutation created, then later assign it to a click event using the v-on directive.

      To create an action, open the src/store/index.js file in your text editor. In the actions section of the store, create a function. Like the getter, the function name will be how you reference the action later. Name this function addToFavorites:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
        state: {
          firstName: 'John',
          lastName: 'Doe',
          favorites: [] // will store favorites here
        },
        mutations: {
      
        },
        actions: {
          addToFavorites() {
      
          }
        },
        getters: {
          fullName: function (state) {
            return `${state.firstName} ${state.lastName}`
          }
      }
      })
      

      An action accepts two arguments: the context, or the Vue app itself, and the payload, or the data that you want to add to the store. The context has a commit method associated with it that you will use to call a mutation you will make later:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
        state: {
          firstName: 'John',
          lastName: 'Doe',
          favorites: []
        },
        mutations: {
      
        },
        actions: {
          addToFavorites(context, payload) {
            context.commit('UPDATE_FAVORITES', payload)
          }
        },
        getters: {
          fullName: function (state) {
            return `${state.firstName} ${state.lastName}`
          }
       }
      })
      

      The commit method also accepts two arguments: the name of the mutation to call and the payload or the data that the mutation will replace the state with.

      In this code, you are naming the mutation UPDATE_FAVORITES. Mutation names should be agnostic and not named after a specific action. For example, a mutation like ADD_FAVORITE and REMOVE_FAVORITE implies a logic, like removing or adding a piece of data. This is not ideal, since mutations should have one job and one job only: update the state. To differentiate between adding and removing data, you could have two different actions that remove or add a favorite airport from the array, which then execute a single mutation called UPDATE_FAVORITES that updates the array with whatever was passed in. Minimizing the amount of mutations you have in your store will help make your Vuex store easier to manage as it grows larger in complexity and size.

      Next, add some logic to this action. When you add an airport as a “favorite”, you will add that payload (the airport data) to the existing array. To do that, you can use the push method in JavaScript:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
        state: {
          firstName: 'John',
          lastName: 'Doe',
          favorites: []
        },
        mutations: {
      
        },
        actions: {
          addToFavorites(context, payload) {
            const favorites = context.state.favorites
            favorites.push(payload)
            context.commit('UPDATE_FAVORITES', favorites)
          }
        },
        getters: {
          fullName: function (state) {
            return `${state.firstName} ${state.lastName}`
          }
       }
      })
      

      At this point, your action is set up to to add the payload to your favorites array then call a mutation with the mutated array as the new data. Next, you will define the UPDATE_FAVORITES mutation. Add the following code to set the favorites array as a new array:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
        state: {
            firstName: 'John',
          lastName: 'Doe',
          favorites: []
          },
        mutations: {
          UPDATE_FAVORITES(state, payload) {
            state.favorites = payload
          }
        },
        actions: {
          addToFavorites(context, payload) {
            const favorites = context.state.favorites
            favorites.push(payload)
            context.commit('UPDATE_FAVORITES', favorites)
          }
        },
        getters: {
          fullName: function (state) {
            return `${state.firstName} ${state.lastName}`
          }
       }
      })
      

      Now that you have your action and your mutation, you can save this file.

      To execute this action, you can call a dispatch event when the user clicks on a card. You will do this with the v-on directive.

      Open the App.vue file in your text editor. On the <airport-card /> component, add the v-on directive shorthand syntax (@) with the event being click:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <p>{{ $store.getters.fullName }}</p>
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" @click="$store.dispatch('addToFavorites', airport)" />
          </div>
          <h2 v-if="$store.state.favorites.length">Favorites</h2>
          <div v-for="airport in $store.state.favorites" :key="airport.abbreviation">
            <airport-card :airport="airport"  />
          </div>
        </div>
      </template>
      ...
      

      The dispatch function accepts two arguments: the action name and the payload data that you are sending to the action.

      Save this file and open it in your browser. Now, when you click on an airport card, the action will call the mutation that updates the state and adds the airport to the favorites property.

      A favorite airport added to the favorite airports section after a Vuex mutation was executed.

      In this step, you expanded on the Vuex store that you created earlier. You created an action that copies an array and pushes a new item to that array. That action called a mutation that in turn updated the state. In addition to that, you learned about getters and how they can be leveraged to create new properties by combining or modifying read-only values in the Vuex store.

      In the final step, you will implement Vuex modules. Modules are a great way to break up your Vuex store into smaller Vuex stores. This is useful as your Vuex store growers larger in size and complexity.

      Step 4 — Composing Vuex Modules

      Modules are smaller Vuex stores that are combined into a single Vuex store. This is similar to how multiple Vue.js components are imported into a single .vue file, such as App.vue. In this step, you are going to separate this Vuex store into two separate modules. One module will be for a user state, and the other will be specific to airport state, actions, and mutations.

      In your terminal, cd into the store directory and use the touch command to create two separate files.

      • touch src/store/user.module.js
      • touch src/store/airports.module.js

      Inside of the user.module.js file, create an object that will be exported by default by adding the following code:

      favorite-airports/src/store/user.module.js

      export default {
        namespaced: true
      }
      

      You are also adding the property namespaced with its value as true. The namespace property will make it so you can reference the module name while accessing a property with dot notation later.

      Inside of this object, you will add the state and getter information that is associated with a user:

      favorite-airports/src/store/user.module.js

      export default {
        namespaced: true,
        state: {
          firstName: 'John',
          lastName: 'Doe'
        },
        getters: {
          fullName: function (state) {
            return `${state.firstName} ${state.lastName}`
          }
        }
      }
      

      The user module contains everything that you need for user information. Save and exit from the file.

      Go ahead and do the same thing in the airports.module.js file. Open the airports.module.js file in your text editor and add the following:

      favorite-airports/src/store/airports.module.js

      export default {
        state: {
          favorites: []
        },
        mutations: {
          UPDATE_FAVORITES(state, payload) {
            state.favorites = payload
          }
        },
        actions: {
          addToFavorites(context, payload) {
            const favorites = context.state.favorites
            favorites.push(payload)
            context.commit('UPDATE_FAVORITES', favorites)
          }
        },
      }
      

      Now that you have put the airport-related mutations, actions, and state, you can save your airports.module.js.

      Next, import these two files into the main store/index.js file. In your text editor, open the store/index.js file and remove the state, mutations, actions, and getters properties. Your file will resemble the following snippet:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
      
      })
      

      To register modules, you will need to import them into this index.js file with the following highlighted code:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      import UserModule from './user.module.js'
      import AirportsModule from './airports.module.js'
      
      export default createStore({
      
      })
      

      From here, you will need to have a property called modules with an object as its value. The property names inside of this object will be the names of the Vuex modules. The value of the module name is the imported module itself:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      import UserModule from './user.module.js'
      import AirportsModule from './airports.module.js'
      
      export default createStore({
      modules: {
        user: UserModule,
        airports: AirportsModule
      }
      })
      

      Once you save this file, your modules have now been registered and combined into your single Vuex store. Save store/index.js, then open the App.vue file and update it to reference the newly created modules:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <p>{{ $store.getters['user/fullName'] }}</p>
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" @click="$store.dispatch('addToFavorites', airport)" />
          </div>
          <h2 v-if="$store.state.airports.favorites.length">Favorites</h2>
          <div v-for="airport in $store.state.airports.favorites" :key="airport.abbreviation">
            <airport-card :airport="airport"  />
          </div>
        </div>
      </template>
      ...
      

      Now you have a modular version of your Vuex setup.

      In this step, you segmented your existing Vuex store into smaller chunks called modules. These modules are a great way to group related store properties into a smaller Vuex store. You also updated your App.vue to reference the state and dispatch events in each module.

      Conclusion

      At a high level, state management is all about updating data. In this setup, the data in the state is global throughout your entire application and acts as a single source of truth, which can only be updated with explicit functions in the form of actions and mutations. In this tutorial, you ran through examples of state, mutations, actions, and getters and saw how each of these properties have their own purpose in the update cycle.

      To learn more about Vuex, actions, mutations, and modules, review the official Vuex documentation written by the Vue.js Core Team. For more tutorials on Vue, check out the How To Develop Websites with Vue.js series page.



      Source link

      How to Fix the Missed Scheduled Post Error in WordPress (2 Methods)


      Scheduling your posts in advance on your WordPress site can be a lifesaver. Writing multiple blog posts and scheduling them out should give you peace of mind knowing they’ll be published on time. However, sometimes this feature doesn’t work as expected, and you might end up with the missed scheduled post error.

      Fortunately, there are a few ways to troubleshoot this common issue. Whether you prefer to use a plugin or get under the hood yourself, you should be able to fix the missed scheduled post error and get back to business in no time.

      In this article, we’ll explain what the missed scheduled post error is and look at a few possible causes. Then we’ll share three easy ways to troubleshoot the error and two simple methods for fixing it. Let’s get started!

      What the Missed Scheduled Post Error Is (And What Causes It)

      When you schedule a post in advance, WordPress uses a “cron job” to publish it. “Cron” is a software utility that schedules tasks, while a cron job is the task itself. Typically, you’ll see cron used to handle repetitive tasks.

      Since WordPress is expected to run in a variety of environments, it can’t rely on traditional cron, which is intended to work in Unix-like operating systems. Instead, WordPress has WP-cron, which can simulate a system cron. When something goes wrong with the cron job publishing your scheduled posts, you’ll see the missed schedule error.

      There are a few potential causes of this error. One possible scenario is that your server or a plugin could be affecting your cron jobs. Common culprits include caching plugins. While these plugins are valuable for improving your site’s performance, they can also interfere with WP-cron.

      Another cause stems from the fact that WordPress uses simulated cron jobs. These simulations require someone to visit your website at the same time that the task is supposed to be run. If no one lands on your website at the right time, your post won’t be published.

      Obviously, if your site has experienced some downtime, then no one can visit it. This could prevent a cron job from running as expected.

      Skip the Stress

      Avoid troubleshooting when you sign up for DreamPress. Our friendly WordPress experts are available 24/7 to help solve website problems — big or small.

      How to Troubleshoot the Missed Scheduled Post Error (3 Ways)

      If you’ve encountered the missed scheduled post error, there are a few simple ways to go about troubleshooting it. Let’s take a closer look at them.

      1. Check Your Timezone Settings

      If the timezone you’ve set in WordPress doesn’t match the timezone you use for publishing posts, it’s unlikely that these posts will go live as expected. Fortunately, there is a simple fix for this timezone issue, so it’s a good place to start.

      To check your timezone settings, go to your admin dashboard and navigate to Settings > General. Scroll down, and you should see a dropdown menu where you can select your timezone.

      Adjusting the timezone in General Settings.

      Using the dropdown, select the timezone you want to use for your WordPress website. Then, click on the Save Changes button, and you’re all set!

      2. Clear the WordPress Cache

      As we mentioned earlier, occasionally a WordPress plugin can interfere with the cache being cleared. So while caching can help to speed up your website, sometimes it can make it difficult for changes to take effect the way they should. Fortunately, clearing the WordPress cache manually is a simple process.

      The first step is to clear your browser’s cache. The steps you’ll need to follow will differ depending on which browser you use. If you’re still not seeing your scheduled posts, try clearing the WordPress cache as well.

      If you’re using a caching plugin, you’ll need to clear your cache through it. For example, if you’re using the WP Super Cache, go to Settings > WP Super Cache and select the Easy tab. Next, click on the Delete Cache button.

      How to delete the cache using WP Super Cache.

      Even if you use a different caching plugin, chances are the steps will be similar. Most of these plugins feature one-click cache deletion.

      Managed hosting plans tend to work a little differently. If you’re a DreamPress customer, you should already have the Proxy Cache Purge plugin installed to handle this for you. However, you can also purge the cache manually.

      To clear the entire cache, you just need to hover over the Cache icon in your dashboard menu and select Purge Cache (All Pages).

      Purging all pages using the Proxy Cache Purge plugin.

      You can also purge the cache for an individual post or page. To do this, you’ll need to navigate to the desired post either by entering the URL or by locating it in your dashboard and clicking on the View option.

      Once again, you’ll need to hover over the Cache icon in your dashboard menu, but this time you’ll have the option to Purge Cache (This Page).

      Purging a single page using the Proxy Cache Purge plugin.

      Don’t worry if your scheduled posts still aren’t showing up. There is one more troubleshooting method to try.

      3. Increase the WordPress Memory Limit

      Finally, it’s possible that your WordPress site needs more memory than what is currently allocated. An easy way to increase the memory limit is by editing the wp-config.php file.

      To access this file, you’ll need to use a Secure File Transfer Protocol (SFTP) client, such as FileZilla. Alternatively, you can use the file manager in your hosting account.

      If you have a DreamHost account, start by navigating to Websites > Files in the sidebar. Next, locate your domain and click on the Manage Files button.

      Accessing the file manager through your DreamPress hosting account.

      This will take you to the file manager. To access your site’s directory, you’ll need to open the folder labeled with your domain name. Inside it, you should find the wp-config.php file.

      If you’re using FileZilla, the first step is to connect to your website. You may need to obtain your credentials from your web host if this is your first time using it. Once connected, locate the wp-config.php file.

      Locating the wp-config.php file using FileZilla.

      Next, right-click on this file to download it. Now you can use a text editor to open and edit the file. Add the following line of code anywhere before the line that reads /* That’s all, stop editing! Happy blogging. */:

      define( 'WP_MEMORY_LIMIT', 'XXXM' );

      You’ll want to replace the “XXX” with the amount of memory you’d like allocated to PHP, such as “96MB”. Remember to save your file before closing it. You can then use FileZilla to re-upload your updated wp-config.php file.

      How to Fix the WordPress Missed Scheduled Post Error in WordPress (2 Methods)

      If none of the above troubleshooting methods worked or if the error keeps happening, you might want to try a more advanced fix. Let’s look at two effective ways to resolve the missed scheduled post issue.

      1. Use a Plugin

      Scheduled Post Trigger is a free plugin that you can use to ensure that your cron job runs properly. It works by checking if any scheduled posts have been missed each time a visitor lands on your website.

      The Scheduled Post Trigger plugin.

      When it comes to plugins, setup doesn’t get much easier than this. Simply install and activate the plugin, and you’re ready to go.

      However, it’s best not to rely on this plugin for a permanent solution. Like with WP-cron, caching plugins can interfere with the Scheduled Post Trigger. If you experience any compatibility issues with this plugin, you might want to troubleshoot by disabling your other plugins one at a time to find the culprit.

      2. Manage Cron Jobs Directly Through Your Server

      Another option is to manage cron jobs directly through your server. This takes a couple of steps, but we’ll walk you through them.

      Step 1: Disable WordPress’ Crons

      The first step is to disable WordPress’ default crons. Otherwise, any new cron jobs you create may not function properly.

      To do this, you’ll once again need to access your wp-config.php file via SFTP or the file manager in your hosting account. This time, you can add this line of code to stop WordPress’ crons. You can add it anywhere above the /* That’s all, stop editing! Happy blogging. */ comment:

      define(‘DISABLE_WP_CRON’,true);

      Finally, save your changes. You can then re-upload your wp-config.php file and move on to adding your own cron job.

      Step 2: Add a New Cron Job

      Start at the DreamHost panel. Log in to your hosting account, then go to More > Cron Jobs. Click on the Add New Cron Job button.

      Adding a new cron job from the DreamHost panel.

      From here, you can fill in the required fields to set up your custom cron job. Having some knowledge of UNIX commands will be helpful to do this, but we’ll show you the basics that should get the job done.

      Adding a cron job manually.

      First, choose a User. It’ll need to be a shell user since they’re the only ones that can run cron jobs. You can also add a title to help you remember this job, such as Scheduled Post Trigger.

      Next, you can add an email to send the output to. If you don’t need an alert every time your site checks for scheduled posts – which you probably don’t – simply leave it blank.

      Now you’ll need to enter the command. First, take a look at this sample command from Zero Point Development:

      /usr/bin/php -q /home/zpd/public_html/wp-cron.php

      You can copy and paste the first part (/usr/bin/php -q) as is. However, the second part will take some customizing. You’ll need to write your own unique path to the wp-cron.php file. For example, the following code represents DreamHost’s server standard:

      /usr/bin/php -q /home/username/domainname/wp-cron.php

      If you’re not sure what that looks like, check out our support article on creating cron jobs. You can also reach out to our support team if you need further assistance.

      Once you’ve set up your cron job, you can schedule when it’ll run. We recommend somewhere around five minutes. Then, click on the Add button. That’s it! You can always return to the Crontab if you need to troubleshoot your cron job in the future.

      Have Another Error? We’ve Got a WordPress Tutorial for That

      Do you want to learn how to resolve other technical problems on your site? We’ve put together a number of guides to help you troubleshoot every common WordPress error:

      And if you’re looking for more information and best practices for running a WordPress site, check out our WordPress Tutorials section. This is a collection of expert-written guides designed to help you navigate the WordPress dashboard like a pro.

      Take Your WordPress Website to the Next Level

      Whether you need help navigating the WordPress dashboard, fixing incorrect database credentials, or finding the plugins folder, we can help! Subscribe to our monthly digest so you never miss an article.

      WP Scheduled Post Made Easy

      The WordPress missed scheduled post error typically occurs when something goes wrong with the cron job that publishes your content. Starting out with a few basic troubleshooting methods, such as checking timezone settings or clearing the WordPress cache, may be enough to get your posts published reliably.

      In this post, we’ve also looked at two ways to resolve the WordPress missed schedule error:

      1. Use a plugin such as Scheduled Post Trigger to ensure that your cron jobs run properly.
      2. Manage cron jobs directly through your server.

      Thanks to DreamHost’s intuitive, user-friendly panel, managing your cron jobs and other maintenance tasks is a straightforward process. Check out our WordPress-optimized hosting plans to get started!



      Source link