Proposal: Composite Queries

This proposal has been extracted from the long discussion in Inter-Canister Query Calls (Community Consideration). We will present and discuss it in the upcoming session of Technical Working Group: Scalability & Performance on October 20.

Composite queries

Background & Problem statement

Canisters have two types of methods: updates and queries. In contrast to updates, queries are not composable. In other words, a query cannot call other queries. This limitation makes it difficult to build scalable dapps that shard data across multiple canisters. Supporting calls in query methods is one of the most requested features.

We have an experimental prototype implementation of the feature. The prototype has two limitations:

  1. it does not support cross-subnet calls, which means that a query can only call queries of other canisters that are on the same subnet as the original canister.
  2. it does not support replicated execution, which means that a query is executed on a single machine of a subnet and its result doesn’t go through consensus.

Implementing the missing parts is a large (1-2 years) engineering effort due to technical challenges outlined in this forum post.


Our proposal is to release the prototype with the known limitations and postpone the full implementation to the future.

We expect the following work before the prototype can be released:

  • Add a new query type for backwards compatibility.
  • Implement protection against malicious canisters.
  • Improve performance.
  • Write the specification.

We need a new query type because the existing queries can be executed both in non-replicated and replicated mode. Since the prototype doesn’t support replicated execution, releasing it without a new query type would break the contract of the existing queries.

We propose to name the new query type composite_query and propose the following changes:

  • Add a new public entry-point type in the WebAssembly module of canisters: canister_composite_query <name>.
  • Add a new composite_query keyword in Motoko, Rust CDK, and Candid.

Composite queries will be similar to the existing regular queries except that they can call other queries and they do not support replicated execution. Note that composite queries can call both regular and composite queries. Update methods cannot call composite queries.

Future outlook

In the future it should be possible to implement support for both cross-subnet calls and replicated execution.

For cross-subnet calls we would need to solve the technical challenge related to the security of caller_id (details) and rewrite the existing implementation of query calls to use an asynchronous/distributed algorithm.

Replicated execution support has more complexity and unknowns. Core components of the Internet Computer such as state manager, message routing, execution would need to support multiple versions of the same canister in the replicated state.

If both features are implemented, we would be able to unify composite queries and regular queries.


We could attempt to avoid a new query type by implementing support for replicated execution before releasing the feature. We expect this to be a significant engineering effort that would delay the feature by at least 1-2 years.


@Manu, @stefan-kaestle, @ulan


Certainly having some functionality is better than none, but it is a real bummer that devs have to think about sub nets, especially considering the lack of control you have over where your canisters are created.

Would a prerequisite to this feature be targeted subnet deployment….or even exclusive subnets? I could see something like a canDB instance needing to know that all its shards are out on one subnet.


When a canister creates another canister, the child canister will be on the same subnet. If the shards are created from the same root canister, then the query calls will work for them. I think that would be the workaround until we implement cross-subnet calls.


To answer this question: we are not planning to block on this since there is a workaround.

I agree, it’s a compromise between delivering something quick vs having the optimal solution. We do plan on allowing users to install on a specific subnet when creating a canister from an ingress message. As Ulan said when you install a canister from a canister, you already land on the same subnet.


Could you help me understand how this is different from the way queries work today?

You’re right that in most cases there will be no difference because queries by default use non-replicated execution. However, the existing queries also support replicated execution, which has these use cases:

  • The user wants the result of the query to go through consensus to protect against malicious nodes.
  • A canister executing an update method calls a query method of another canister.

This proposal is now live: 87599.


I hate to be adult, but does this mean that my composite queries can call queries??

If i understand your question correctly you’re asking which types of methods you can call from a composite query method. You can call both queries and composite queries from a composite query method.


Is it correct to say that if you hit a regular query you won’t beable to call beyond that? So any existing queries will need to be upgraded from query to composite query if they want that functionality in the future?

Sounds like a great improvement!

Yes exactly. There is still an advantage to having a query method though: you can call it in replicated mode (so going through consensus, which is slower/more expensive but more secure). You cannot call composite queries in replicated mode (which is why it had to be a new method type).

So it probably makes sense that you would use query methods for things that don’t need to call anything, and composite query methods where you want to make other (composite) query calls in the method.


Note that composite queries can call both regular and composite queries. Update methods cannot call composite queries.

I can foresee the latter being a potential footgun. I’m guessing there’s no way around that, as update methods must run completely in replicated execution mode? Is a world where that restriction is relaxed really that bad?


Is there any way to simplify all of the rules described here? Not being able to call composite queries from update methods I can see being very confusing.

Just to make sure I understand, are you saying that eventually we can completely unify the query/composite query concepts back into query?

I understand the nature of the challenges here, but this is going to introduce significant cognitive complexity to developers, especially new developers. I would suggest simplifying even more if possible.

I also understand that might not be possible.


Just posting this picture from the WG for clarity. More slides can be found here.


Visual question:

Can I call an update from a composite?

This won’t be possible since composite query only run in non-replicated mode while update only run in replicated mode


+1 to @Seb’s comments.

Just to make sure I understand, are you saying that eventually we can completely unify the query/composite query concepts back into query?

Yes, we can unify them when we have replicated execution for composite queries, which the main technical challenge here. If we start working on replicated execution now, I think it will take us at least a year because we would need to make large changes in the core components of the replica.

The trade-off is either to release composite queries now with potentially confusing rules or to work on replicated execution and release the proper query calls in 1-2 years.

Can I call an update from a composite?

It’s a good question. Assuming that in the future we support replicated execution for composite queries, I am not sure if we should allow composite queries to call updates because it would be confusing w.r.t. state changes. I guess an update called from a composite query would need to behave like a query and discard the state changed. That might confuse the users.


Yes, exactly. The only way to safely relax the restriction is to support replicated execution for composite queries. If we allow an update to call a composite query without that support, then each ic0.call_perform() in the composite query will fail with an error.

@ulan thinking a bit more about the trusted caller-id for queries, would it not make sense to restrict composite queries to only call other composite queries, never ordinary queries (removing one arrow from the picture above). Then a regular query can continue to trust its caller id, while the new composite query cannot. Indeed, composite queries probably shouldn’t even be allowed to inspect the caller_id.