diff --git a/jstests/libs/override_methods/set_read_and_write_concerns.js b/jstests/libs/override_methods/set_read_and_write_concerns.js
index b68d6c1..51157cf 100644
--- a/jstests/libs/override_methods/set_read_and_write_concerns.js
+++ b/jstests/libs/override_methods/set_read_and_write_concerns.js
@@ -12,7 +12,7 @@
             " property on the global TestData object");
     }
 
-    const kDefaultReadConcern = {level: TestData.defaultReadConcernLevel};
+    const kDefaultReadConcern = {level: TestData.defaultReadConcernLevel, allowBlockingMajorityReads: true};
     const kDefaultWriteConcern =
         (TestData.hasOwnProperty("defaultWriteConcern")) ? TestData.defaultWriteConcern : {
             w: "majority",
diff --git a/src/mongo/db/clientcursor.cpp b/src/mongo/db/clientcursor.cpp
index 1c747d0..8fb0634 100644
--- a/src/mongo/db/clientcursor.cpp
+++ b/src/mongo/db/clientcursor.cpp
@@ -98,6 +98,8 @@ ClientCursor::ClientCursor(ClientCursorParams params,
 
     cursorStatsOpen.increment();
 
+    _allowBlockingMajorityReads = params.allowBlockingMajorityReads;
+
     if (isNoTimeout()) {
         // cursors normally timeout after an inactivity period to prevent excess memory use
         // setting this prevents timeout of the cursor in question.
diff --git a/src/mongo/db/clientcursor.h b/src/mongo/db/clientcursor.h
index 7602af6..4cd511e 100644
--- a/src/mongo/db/clientcursor.h
+++ b/src/mongo/db/clientcursor.h
@@ -84,10 +84,15 @@ struct ClientCursorParams {
             queryOptions &= ~QueryOption_AwaitData;
     }
 
+    void setAllowBlockingMajorityReads(bool val) {
+        allowBlockingMajorityReads = val;
+    }
+
     std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> exec;
     const NamespaceString nss;
     std::vector<UserName> authenticatedUsers;
     const repl::ReadConcernLevel readConcernLevel;
+    bool allowBlockingMajorityReads = false;
     int queryOptions = 0;
     BSONObj originatingCommandObj;
 };
@@ -136,6 +141,10 @@ public:
         return _readConcernLevel;
     }
 
+    bool getAllowBlockingMajorityReads() const {
+        return _allowBlockingMajorityReads;
+    }
+
     /**
      * Returns a pointer to the underlying query plan executor. All cursors manage a PlanExecutor,
      * so this method never returns a null pointer.
@@ -265,6 +274,7 @@ private:
      */
     void markAsKilled(Status killStatus);
 
+
     /**
      * Disposes this ClientCursor's PlanExecutor. Must be called before deleting a ClientCursor to
      * ensure it has a chance to clean up any resources it is using. Can be called multiple times.
@@ -297,6 +307,8 @@ private:
 
     const repl::ReadConcernLevel _readConcernLevel;
 
+    bool _allowBlockingMajorityReads = false;
+
     CursorManager* _cursorManager;
 
     // Tracks whether dispose() has been called, to make sure it happens before destruction. It is
diff --git a/src/mongo/db/commands/getmore_cmd.cpp b/src/mongo/db/commands/getmore_cmd.cpp
index 257e340..9117586 100644
--- a/src/mongo/db/commands/getmore_cmd.cpp
+++ b/src/mongo/db/commands/getmore_cmd.cpp
@@ -380,9 +380,14 @@ public:
                 repl::ReplicationCoordinator::get(opCtx)->getReplicationMode();
             if (replicationMode == repl::ReplicationCoordinator::modeReplSet &&
                 cursor->getReadConcernLevel() == repl::ReadConcernLevel::kMajorityReadConcern) {
-                opCtx->recoveryUnit()->setTimestampReadSource(
-                    RecoveryUnit::ReadSource::kMajorityCommitted);
-                uassertStatusOK(opCtx->recoveryUnit()->obtainMajorityCommittedSnapshot());
+                if (cursor->getAllowBlockingMajorityReads()) {
+                    opCtx->recoveryUnit()->setTimestampReadSource(
+                        RecoveryUnit::ReadSource::kLastApplied);
+                } else {
+                    opCtx->recoveryUnit()->setTimestampReadSource(
+                        RecoveryUnit::ReadSource::kMajorityCommitted);
+                    uassertStatusOK(opCtx->recoveryUnit()->obtainMajorityCommittedSnapshot());
+                }
             }
 
             const bool disableAwaitDataFailpointActive =
@@ -511,6 +516,29 @@ public:
                 cursorFreer.Dismiss();
             }
 
+
+            auto replCoord = repl::ReplicationCoordinator::get(opCtx);
+            auto lastApplied = replCoord->getMyLastAppliedOpTime();
+            if (replicationMode == repl::ReplicationCoordinator::modeReplSet &&
+                cursor->getReadConcernLevel() == repl::ReadConcernLevel::kMajorityReadConcern) {
+                if (cursor->getAllowBlockingMajorityReads()) {
+                    log() << "#### Blocking majority getMore read: Waiting for optime "
+                          << lastApplied << " to become committed, current commit point: "
+                          << replCoord->getLastCommittedOpTime();
+                    repl::ReadConcernArgs args(lastApplied,
+                                               repl::ReadConcernLevel::kMajorityReadConcern);
+                    auto deadline =
+                        Date_t::now() + Milliseconds(opCtx->getRemainingMaxTimeMillis());
+                    log() << "Remaining maxTimeMS: " << opCtx->getRemainingMaxTimeMillis();
+                    auto s = replCoord->waitUntilOpTimeForReadUntil(opCtx, args, deadline);
+                    uassertStatusOK(s);
+                    opCtx->checkForInterrupt();
+                    log() << "#### Blocking majority getMore read: Optime " << lastApplied
+                          << " became committed";
+                }
+            }
+
+
             // We're about to unpin the cursor as the ClientCursorPin goes out of scope (or delete
             // it,
             // if it has been exhausted). If the
diff --git a/src/mongo/db/commands/run_aggregate.cpp b/src/mongo/db/commands/run_aggregate.cpp
index 7970dca..11682ef 100644
--- a/src/mongo/db/commands/run_aggregate.cpp
+++ b/src/mongo/db/commands/run_aggregate.cpp
@@ -358,9 +358,12 @@ Status runAggregate(OperationContext* opCtx,
 
     std::vector<unique_ptr<PlanExecutor, PlanExecutor::Deleter>> execs;
     boost::intrusive_ptr<ExpressionContext> expCtx;
+
     auto curOp = CurOp::get(opCtx);
+    bool isChangeStream = false;
     {
         const LiteParsedPipeline liteParsedPipeline(request);
+        isChangeStream = liteParsedPipeline.hasChangeStream();
 
         try {
             // Check whether the parsed pipeline supports the given read concern.
@@ -381,10 +384,12 @@ Status runAggregate(OperationContext* opCtx,
 
             // If the read concern is not specified, upgrade to 'majority' and wait to make sure we
             // have a snapshot available.
+            log() << "#### Running change stream aggregation";
             auto& readConcernArgs = repl::ReadConcernArgs::get(opCtx);
             if (!readConcernArgs.hasLevel()) {
                 readConcernArgs =
                     repl::ReadConcernArgs(repl::ReadConcernLevel::kMajorityReadConcern);
+                readConcernArgs.setAllowBlockingReads();
                 uassertStatusOK(waitForReadConcern(opCtx, readConcernArgs, true));
             }
 
@@ -597,6 +602,11 @@ Status runAggregate(OperationContext* opCtx,
             cursorParams.setAwaitData(true);
         }
 
+        // Let all change streams use blocking majority reads implementation when necessary.
+        if(isChangeStream){
+            cursorParams.setAllowBlockingMajorityReads(true);
+        }
+
         auto pin =
             CursorManager::getGlobalCursorManager()->registerCursor(opCtx, std::move(cursorParams));
         cursors.emplace_back(pin.getCursor());
diff --git a/src/mongo/db/pipeline/document_source_lookup_change_post_image.cpp b/src/mongo/db/pipeline/document_source_lookup_change_post_image.cpp
index 3695aaf..a58c2da 100644
--- a/src/mongo/db/pipeline/document_source_lookup_change_post_image.cpp
+++ b/src/mongo/db/pipeline/document_source_lookup_change_post_image.cpp
@@ -108,11 +108,14 @@ Value DocumentSourceLookupChangePostImage::lookupPostImage(const Document& updat
     auto resumeToken =
         ResumeToken::parse(updateOp[DocumentSourceChangeStream::kIdField].getDocument());
 
+    // When doing a post image lookup on mongos, we need to attach the proper read concern to the
+    // query. For post image lookup on a mongod, the lookup happens as a part of the local
+    // aggregation processing.
     const auto readConcern = pExpCtx->inMongos
         ? boost::optional<BSONObj>(BSON("level"
                                         << "majority"
                                         << "afterClusterTime"
-                                        << resumeToken.getData().clusterTime))
+                                        << resumeToken.getData().clusterTime << "allowBlockingMajorityReads" << true))
         : boost::none;
     invariant(resumeToken.getData().uuid);
     auto lookedUpDoc = pExpCtx->mongoProcessInterface->lookupSingleDocument(
diff --git a/src/mongo/db/read_concern.cpp b/src/mongo/db/read_concern.cpp
index d2f1863..5124edb 100644
--- a/src/mongo/db/read_concern.cpp
+++ b/src/mongo/db/read_concern.cpp
@@ -320,6 +320,17 @@ Status waitForReadConcern(OperationContext* opCtx,
         !speculative &&
         replCoord->getReplicationMode() == repl::ReplicationCoordinator::Mode::modeReplSet) {
 
+        // For blocking majority reads, we read from the last applied snapshot, and then wait after
+        // the command is done to make sure the data becomes committed before returning to client.
+        if (readConcernArgs.getLevel() == repl::ReadConcernLevel::kMajorityReadConcern &&
+            readConcernArgs.allowBlockingReads()) {
+            log() << "#### Raw readConcern object: " << readConcernArgs.toBSON();
+            log() << "#### Not waiting for 'majority' read concern since we are going to block "
+                     "later. Setting to read from last applied snapshot.";
+            opCtx->recoveryUnit()->setTimestampReadSource(RecoveryUnit::ReadSource::kLastApplied);
+            return Status::OK();
+        }
+
         const int debugLevel = serverGlobalParams.clusterRole == ClusterRole::ConfigServer ? 1 : 2;
 
         LOG(debugLevel) << "Waiting for 'committed' snapshot to be available for reading: "
diff --git a/src/mongo/db/repl/read_concern_args.cpp b/src/mongo/db/repl/read_concern_args.cpp
index cc56cba..a7d4c20 100644
--- a/src/mongo/db/repl/read_concern_args.cpp
+++ b/src/mongo/db/repl/read_concern_args.cpp
@@ -96,6 +96,14 @@ BSONObj ReadConcernArgs::toBSON() const {
     return bob.obj();
 }
 
+void ReadConcernArgs::setAllowBlockingReads(){
+    _allowBlockingMajorityReads = true;
+}
+
+boost::optional<bool> ReadConcernArgs::allowBlockingReads() const{
+    return _allowBlockingMajorityReads;
+}
+
 bool ReadConcernArgs::isEmpty() const {
     return !_afterClusterTime && !_opTime && !_atClusterTime && !_level;
 }
@@ -192,7 +200,13 @@ Status ReadConcernArgs::initialize(const BSONElement& readConcernElem) {
                                             << " must be either 'local', 'majority', "
                                                "'linearizable', 'available', or 'snapshot'");
             }
-        } else {
+        } else if(fieldName=="allowBlockingMajorityReads"){
+            bool allowBlockingReads;
+            auto res = bsonExtractBooleanFieldWithDefault(readConcernObj, "allowBlockingMajorityReads", false, &allowBlockingReads);
+            _allowBlockingMajorityReads = allowBlockingReads;
+        }
+
+        else {
             return Status(ErrorCodes::InvalidOptions,
                           str::stream() << "Unrecognized option in " << kReadConcernFieldName
                                         << ": "
@@ -314,6 +328,10 @@ void ReadConcernArgs::appendInfo(BSONObjBuilder* builder) const {
         rcBuilder.append(kLevelFieldName, levelName);
     }
 
+    if(_allowBlockingMajorityReads){
+        rcBuilder.append("allowBlockingMajorityReads", _allowBlockingMajorityReads.get());
+    }
+
     if (_opTime) {
         _opTime->append(&rcBuilder, kAfterOpTimeFieldName);
     }
diff --git a/src/mongo/db/repl/read_concern_args.h b/src/mongo/db/repl/read_concern_args.h
index 6ec753d..a9a6996 100644
--- a/src/mongo/db/repl/read_concern_args.h
+++ b/src/mongo/db/repl/read_concern_args.h
@@ -130,6 +130,10 @@ public:
     BSONObj toBSON() const;
     std::string toString() const;
 
+    void setAllowBlockingReads();
+
+    boost::optional<bool> allowBlockingReads() const;
+
 private:
     /**
      *  Read data after the OpTime of an operation on this replica set. Deprecated.
@@ -150,6 +154,9 @@ private:
      * If the read concern was upconverted, the original read concern level.
      */
     boost::optional<ReadConcernLevel> _originalLevel;
+
+    // Allow blocking majority reads POC.
+    boost::optional<bool> _allowBlockingMajorityReads;
 };
 
 }  // namespace repl
diff --git a/src/mongo/db/repl/replication_coordinator_impl.cpp b/src/mongo/db/repl/replication_coordinator_impl.cpp
index 557e27e..5828743 100644
--- a/src/mongo/db/repl/replication_coordinator_impl.cpp
+++ b/src/mongo/db/repl/replication_coordinator_impl.cpp
@@ -1212,7 +1212,10 @@ Status ReplicationCoordinatorImpl::_validateReadConcern(OperationContext* opCtx,
                 "readConcern level 'snapshot' is required when specifying atClusterTime"};
     }
 
+    // If we allow blocking majority reads, then we don't need storage engine support
+    // to do a majority read.
     if (readConcern.getLevel() == ReadConcernLevel::kMajorityReadConcern &&
+        !readConcern.allowBlockingReads() &&
         !_externalState->isReadCommittedSupportedByStorageEngine(opCtx)) {
         return {ErrorCodes::ReadConcernMajorityNotEnabled,
                 str::stream() << "Storage engine does not support read concern: "
diff --git a/src/mongo/db/service_entry_point_common.cpp b/src/mongo/db/service_entry_point_common.cpp
index c6e3b7a..7a2737c 100644
--- a/src/mongo/db/service_entry_point_common.cpp
+++ b/src/mongo/db/service_entry_point_common.cpp
@@ -883,6 +883,24 @@ void execCommandDatabase(OperationContext* opCtx,
             }
             throw;
         }
+
+        //
+        // Block here to make sure returned data is majority committed.
+        //
+        auto lastApplied = replCoord->getMyLastAppliedOpTime();
+        if (readConcernArgs.getLevel() == repl::ReadConcernLevel::kMajorityReadConcern &&
+            readConcernArgs.allowBlockingReads() == boost::optional<bool>(true)) {
+            log() << "#### Blocking majority read: Waiting for optime " << lastApplied
+                  << " to become committed, current commit point: "
+                  << replCoord->getLastCommittedOpTime();
+            repl::ReadConcernArgs args(lastApplied, repl::ReadConcernLevel::kMajorityReadConcern);
+            auto deadline = Date_t::now() + Milliseconds(opCtx->getRemainingMaxTimeMillis());
+            auto s = replCoord->waitUntilOpTimeForReadUntil(opCtx, args, deadline);
+            uassertStatusOK(s);
+            opCtx->checkForInterrupt();
+            log() << "#### Blocking majority read: Optime " << lastApplied << " became committed";
+        }
+
     } catch (const DBException& e) {
         if (e.code() == ErrorCodes::SnapshotTooOld) {
             // SnapshotTooOld errors indicate that PIT ops are failing to find an available snapshot
