[SERVER-27269] In sharded cluster, can't read from view whose pipeline has $collStats and is a view on another view Created: 02/Dec/16  Updated: 27/Oct/23  Resolved: 02/Aug/19

Status: Closed
Project: Core Server
Component/s: Aggregation Framework
Affects Version/s: 3.4.0
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: Kyle Suarez Assignee: Backlog - Query Team (Inactive)
Resolution: Gone away Votes: 0
Labels: open_todo_in_code, query-44-grooming, read-only-views, todo_in_code
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Backports
Related
related to SERVER-27414 On mongos, cannot query view whose fi... Closed
related to SERVER-43472 Complete TODO listed in SERVER-27269 Closed
related to SERVER-44209 Complete TODO listed in SERVER-27269 Closed
is related to SERVER-26765 Move views tests into jsCore Closed
Assigned Teams:
Query
Operating System: ALL
Backport Requested:
v3.4
Sprint: Query 2016-12-12
Participants:

 Description   

In a sharded configuration, users cannot query a view if its backing namespace is also a view and its pipeline contains $collStats. More concretely,

db.createView("firstView", "somethingElse", []);
db.createView("collStatsView", "firstView", [ { $collStats: {} } ]);

All queries against collStatsView incorrectly fail with "command not supported on view".

This bug does not affect views that don't have $collStats in their pipelines. It also does not affect views whose pipelines contain $collStats but have a collection as their backing namespace.



 Comments   
Comment by David Storch [ 02/Aug/19 ]

This no longer appears to be an issue:

MongoDB Enterprise mongos> db.createView("firstView", "somethingElse", []);
{
	"ok" : 1,
	"operationTime" : Timestamp(1564758135, 5),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1564758135, 5),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}
MongoDB Enterprise mongos> db.createView("collStatsView", "firstView", [ { $collStats: {} } ]);
{
	"ok" : 1,
	"operationTime" : Timestamp(1564758140, 1),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1564758140, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}
MongoDB Enterprise mongos> db.collStatsView.find()
{ "ns" : "test.firstView", "shard" : "__unknown_name__-rs1", "host" : "storchbox:20001", "localTime" : ISODate("2019-08-02T15:02:25.771Z") }
MongoDB Enterprise mongos> db.c.drop()
\true
MongoDB Enterprise mongos> db.c.insert({_id: 1})
WriteResult({ "nInserted" : 1 })
MongoDB Enterprise mongos> db.myView.drop()
true
MongoDB Enterprise mongos> db.createView("myView", "c", [])
{
	"ok" : 1,
	"operationTime" : Timestamp(1564758175, 5),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1564758175, 5),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}
MongoDB Enterprise mongos> db.runCommand({aggregate: "myView", pipeline: [{$collStats: {}}], cursor: {batchSize: 0}})
{
	"cursor" : {
		"id" : NumberLong("5723350260413095662"),
		"ns" : "test.myView",
		"firstBatch" : [ ]
	},
	"ok" : 1,
	"operationTime" : Timestamp(1564758175, 3),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1564758175, 5),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

Closing as Gone Away.

Comment by Asya Kamsky [ 01/Aug/17 ]

david.storch this seems to be partially fixed - at least the code in the last comment runs successfully without error.

Of original report some of the examples now work, others give the same error.

Comment by David Storch [ 05/Dec/16 ]

kyle.suarez and I discovered a few more related problems while investigating a fix. The problems all stem from the fact that ViewCatalog::resolveView() can return a view namespace rather than a collection namespace in the case of $collStats. We have several places where calling code always expects successful resolution to yield a collection rather than another view. ViewShardingCheck is one such place; the aggregation command's cursor-storing logic is another:

> db.c.drop()
true
> db.c.insert({_id: 1})
WriteResult({ "nInserted" : 1 })
> db.myView.drop()
true
> db.createView("myView", "c", [])
{ "ok" : 1 }
> db.runCommand({aggregate: "myView", pipeline: [{$collStats: {}}], cursor: {batchSize: 0}})
{
	"ok" : 0,
	"errmsg" : "Aggregation has more results than fit in initial batch, but can't create cursor since collection test.myView doesn't exist",
	"code" : 17391,
	"codeName" : "Location17391"
}

The problem here is that cursors over views are registered with the backing namespace after resolution. If the resolution process doesn't return the backing namespace, then the namespace on which we attempt to register the cursor won't exist!

The intended design of the views feature was that views always can fully resolve to a valid pipeline on the backing collection, but $collStats currently breaks this rule. The best way to fix therefore involves changing ViewCatalog::resolveView() to return the backing collection even for views involving $collStats.

Comment by Kyle Suarez [ 02/Dec/16 ]

The problem is that ViewShardingCheck always assumes that the ResolvedView returned by ViewCatalog::resolveView is a collection. However, consider when $collStats is run on a view – we resolve the view one level to the first backing namespace, then stop resolution. If we have a view on a view, it's possible for the ResolvedView to still be a view.

A fix for this would be to first check if the resolved view is a view before calling ViewShardingCheck::collectionIsSharded(); e.g.

diff --git a/src/mongo/db/views/view_sharding_check.cpp b/src/mongo/db/views/view_sharding_check.cpp
index cc662ff..fc564d2 100644
--- a/src/mongo/db/views/view_sharding_check.cpp
+++ b/src/mongo/db/views/view_sharding_check.cpp
@@ -61,6 +61,9 @@ StatusWith<BSONObj> ViewShardingCheck::getResolvedViewIfSharded(OperationContext
     }
 
     const auto& sourceNss = resolvedView.getValue().getNamespace();
+    if (db->getViewCatalog()->lookup(opCtx, sourceNss.ns())) {
+        return BSONObj();
+    }
     const auto isPrimary =
         repl::ReplicationCoordinator::get(opCtx->getClient()->getServiceContext())
             ->canAcceptWritesForDatabase(db->name());

Generated at Thu Feb 08 04:14:41 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.