Batching requests to subgraphs in GraphQL API federation setting

Hello!

I’m using Tyk as an API federation gateway where I a supergraph and several subgraphs.

My problem is an inefficiency of calling subgraphs that extend GraphQL typesu using API federation. Here’s an example:

Subgraph 1 GraphQL schema (notice multiple id fields):

type MyType {
  id1: ID!
  id2: ID!
}

Subgraph 2 GraphQL schema:

type MyType @key(fields: "id1 id2") @extends {
  id1: ID! @external
  id2: ID! @external
  addedField: String
}

It works well. But I notice multiple requests to the second subgraph provider when the requests look like this:

Query #1:

query($representations: [_Any!]!) {
  _entities(representations: $representations) {
     ... on MyType {
       addedField
     }
  }
}
"variables":{
  "representations":[
    {
      "id1":"id1_1",
      "id2":"id2_1",
      "__typename":"MyType"
    }
  ]
}

Query #2:

query($representations: [_Any!]!) {
  _entities(representations: $representations) {
     ... on MyType {
       addedField
     }
  }
}
"variables":{
  "representations":[
    {
      "id1":"id1_2",
      "id2":"id2_2",
      "__typename":"MyType"
    }
  ]
}

and so on.

So if my first subgraph provider returns 100 instances of MyType then Tyk issues 100 requests to the second subgraph provider.

Question: why can’t Tyk issue a single request to the second graphql provider when all 100 entity representations are packed? I.e. is the following request possible:

# query #1
query($representations: [_Any!]!) {
  _entities(representations: $representations) {
     ... on MyType {
       addedField
     }
  }
}
"variables":{
  "representations":[
    {
      "id1":"id1_1",
      "id2":"id2_1",
      "__typename":"MyType"
    },
    {
      "id1":"id1_2",
      "id2":"id2_2",
      "__typename":"MyType"
    }
    , // and so on
  ]
}

My setting:

tyk-gateway OSS version 4.1.0-rc13

Supergraph:

{
  "name": "GraphQL API",
  "expiration": "2099-12-31 23:59",
  "slug": "",
  "listen_port": 0,
  "protocol": "",
  "enable_proxy_protocol": false,
  "api_id": "graphql",
  "org_id": "",
  "use_keyless": false,
  "use_oauth2": false,
  "use_openid": false,
  "openid_options": {
    "providers": null,
    "segregate_by_client": false
  },
  "oauth_meta": {
    "allowed_access_types": null,
    "allowed_authorize_types": null,
    "auth_login_redirect": ""
  },
  "auth": {
    "name": "",
    "use_param": false,
    "param_name": "",
    "use_cookie": false,
    "cookie_name": "",
    "disable_header": false,
    "auth_header_name": "",
    "use_certificate": false,
    "validate_signature": false,
    "signature": {
      "algorithm": "",
      "header": "",
      "use_param": false,
      "param_name": "",
      "secret": "",
      "allowed_clock_skew": 0,
      "error_code": 0,
      "error_message": ""
    }
  },
  "auth_configs": {
    "authToken": {
      "name": "",
      "use_param": false,
      "param_name": "",
      "use_cookie": false,
      "cookie_name": "",
      "disable_header": false,
      "auth_header_name": "Authorization",
      "use_certificate": false,
      "validate_signature": false,
      "signature": {
        "algorithm": "",
        "header": "",
        "use_param": false,
        "param_name": "",
        "secret": "",
        "allowed_clock_skew": 0,
        "error_code": 0,
        "error_message": ""
      }
    }
  },
  "use_basic_auth": false,
  "basic_auth": {
    "disable_caching": false,
    "cache_ttl": 0,
    "extract_from_body": false,
    "body_user_regexp": "",
    "body_password_regexp": ""
  },
  "use_mutual_tls_auth": false,
  "client_certificates": null,
  "upstream_certificates": null,
  "pinned_public_keys": null,
  "enable_jwt": true,
  "use_standard_auth": false,
  "use_go_plugin_auth": false,
  "enable_coprocess_auth": false,
  "jwt_signing_method": "hmac",
  "jwt_source": "",
  "jwt_identity_base_field": "",
  "jwt_client_base_field": "",
  "jwt_policy_field_name": "",
  "jwt_default_policies": [
    "graphql-default"
  ],
  "jwt_issued_at_validation_skew": 0,
  "jwt_expires_at_validation_skew": 0,
  "jwt_not_before_validation_skew": 0,
  "jwt_skip_kid": false,
  "scopes": {
    "jwt": {
      "scope_claim_name": "",
      "scope_to_policy": null
    },
    "oidc": {
      "scope_claim_name": "",
      "scope_to_policy": null
    }
  },
  "jwt_scope_to_policy_mapping": {},
  "jwt_scope_claim_name": "sub",
  "notifications": {
    "shared_secret": "",
    "oauth_on_keychange_url": ""
  },
  "enable_signature_checking": false,
  "hmac_allowed_clock_skew": 0,
  "hmac_allowed_algorithms": null,
  "request_signing": {
    "is_enabled": false,
    "secret": "",
    "key_id": "",
    "algorithm": "",
    "header_list": null,
    "certificate_id": "",
    "signature_header": ""
  },
  "base_identity_provided_by": "",
  "definition": {
    "enabled": false,
    "name": "",
    "default": "",
    "location": "",
    "key": "",
    "strip_path": false,
    "strip_versioning_data": false,
    "versions": null
  },
  "version_data": {
    "not_versioned": true,
    "default_version": "",
    "versions": {
      "Default": {
        "name": "Default",
        "expires": "",
        "paths": {
          "ignored": null,
          "white_list": null,
          "black_list": null
        },
        "use_extended_paths": true,
        "extended_paths": {},
        "global_headers": null,
        "global_headers_remove": null,
        "global_response_headers": null,
        "global_response_headers_remove": null,
        "ignore_endpoint_case": false,
        "global_size_limit": 0,
        "override_target": ""
      }
    }
  },
  "uptime_tests": {
    "check_list": null,
    "config": {
      "expire_utime_after": 0,
      "service_discovery": {
        "use_discovery_service": false,
        "query_endpoint": "",
        "use_nested_query": false,
        "parent_data_path": "",
        "data_path": "",
        "port_data_path": "",
        "target_path": "",
        "use_target_list": false,
        "cache_timeout": 0,
        "endpoint_returns_list": false
      },
      "recheck_wait": 0
    }
  },
  "proxy": {
    "preserve_host_header": false,
    "listen_path": "/api/",
    "disable_strip_slash": false,
    "strip_listen_path": true,
    "enable_load_balancing": false,
    "target_list": null,
    "check_host_against_uptime_tests": false,
    "service_discovery": {
      "use_discovery_service": false,
      "query_endpoint": "",
      "use_nested_query": false,
      "parent_data_path": "",
      "data_path": "",
      "port_data_path": "",
      "target_path": "",
      "use_target_list": false,
      "cache_timeout": 0,
      "endpoint_returns_list": false
    },
    "transport": {
      "ssl_insecure_skip_verify": false,
      "ssl_ciphers": null,
      "ssl_min_version": 0,
      "ssl_max_version": 0,
      "ssl_force_common_name_check": false,
      "proxy_url": ""
    }
  },
  "disable_rate_limit": false,
  "disable_quota": false,
  "custom_middleware": {
    "pre": [
      {
        "name": "PreHook",
        "path": "",
        "require_session": false,
        "raw_body_only": false
      }
    ],
    "post": null,
    "post_key_auth": null,
    "auth_check": {
      "name": "",
      "path": "",
      "require_session": false,
      "raw_body_only": false
    },
    "response": null,
    "driver": "python",
    "id_extractor": {
      "extract_from": "",
      "extract_with": "",
      "extractor_config": null
    }
  },
  "custom_middleware_bundle": "graphql.zip",
  "cache_options": {
    "cache_timeout": 0,
    "enable_cache": false,
    "cache_all_safe_requests": false,
    "cache_response_codes": null,
    "enable_upstream_cache_control": false,
    "cache_control_ttl_header": "",
    "cache_by_headers": null
  },
  "session_lifetime": 0,
  "active": true,
  "internal": false,
  "auth_provider": {
    "name": "",
    "storage_engine": "",
    "meta": null
  },
  "session_provider": {
    "name": "",
    "storage_engine": "",
    "meta": null
  },
  "event_handlers": {
    "events": null
  },
  "enable_batch_request_support": true,
  "enable_ip_whitelisting": false,
  "allowed_ips": null,
  "enable_ip_blacklisting": false,
  "blacklisted_ips": null,
  "dont_set_quota_on_create": false,
  "expire_analytics_after": 0,
  "response_processors": null,
  "CORS": {
    "enable": true,
    "allowed_origins": [
      "*"
    ],
    "allowed_methods": [
      "POST"
    ],
    "allowed_headers": [
      "Origin",
      "Content-Type",
      "Accept",
      "Authorization"
    ],
    "exposed_headers": [],
    "allow_credentials": false,
    "max_age": 3600,
    "options_passthrough": false,
    "debug": false
  },
  "domain": "",
  "certificates": null,
  "do_not_track": false,
  "tags": null,
  "enable_context_vars": false,
  "config_data": null,
  "tag_headers": null,
  "global_rate_limit": {
    "rate": 0,
    "per": 0
  },
  "strip_auth_data": false,
  "enable_detailed_recording": false,
  "graphql": {
    "schema": "",
    "enabled": true,
    "engine": {
      "field_configs": [],
      "data_sources": []
    },
    "type_field_configurations": [],
    "execution_mode": "supergraph",
    "proxy": {
      "auth_headers": {}
    },
    "subgraph": {
      "sdl": "type MyType {id1 ID!\n id2 ID!\n addedField: String\n}\ntype Query {myTypes: [MyType!]}"
    },
    "supergraph": {
      "updated_at": "2022-03-11T16:08:13.774Z",
      "subgraphs": [
        {
          "api_id": "service1",
          "name": "GraphQL API 1",
          "url": "tyk://service1/",
          "sdl": "type MyType {id1 ID!\n id2 ID!\n}\ntype Query {myTypes: [MyType!]}"
        },
        {
          "api_id": "service2",
          "name": "GraphQL API 2",
          "url": "tyk://service2/",
          "sdl": "type MyType @key(fields: "id1 id2") @extends {\n  id1: ID! @external\n  id2: ID! @external\n  addedField: String\n}"
        }
      ],
      "merged_sdl": "type MyType {id1 ID!\n id2 ID!\n addedField: String\n}\ntype Query {myTypes: [MyType!]}",
      "global_headers": {
        "Authorization": "{{.request.headers.Authorization}}"
      },
      "disable_query_batching": false
    },
    "version": "2",
    "playground": {
      "enabled": true,
      "path": "/playground"
    },
    "last_schema_update": "2022-03-11T16:08:13.774Z"
  }
}

Subgraph 1:

{
  "name": "GraphQL API 1",
  "expiration": "2099-12-31 23:59",
  "slug": "",
  "listen_port": 0,
  "protocol": "",
  "enable_proxy_protocol": false,
  "api_id": "service1",
  "org_id": "",
  "use_keyless": true,
  "use_oauth2": false,
  "use_openid": false,
  "openid_options": {
    "providers": null,
    "segregate_by_client": false
  },
  "oauth_meta": {
    "allowed_access_types": null,
    "allowed_authorize_types": null,
    "auth_login_redirect": ""
  },
  "auth": {
    "name": "",
    "use_param": false,
    "param_name": "",
    "use_cookie": false,
    "cookie_name": "",
    "disable_header": false,
    "auth_header_name": "",
    "use_certificate": false,
    "validate_signature": false,
    "signature": {
      "algorithm": "",
      "header": "",
      "use_param": false,
      "param_name": "",
      "secret": "",
      "allowed_clock_skew": 0,
      "error_code": 0,
      "error_message": ""
    }
  },
  "auth_configs": {
    "authToken": {
      "name": "",
      "use_param": false,
      "param_name": "",
      "use_cookie": false,
      "cookie_name": "",
      "disable_header": false,
      "auth_header_name": "Authorization",
      "use_certificate": false,
      "validate_signature": false,
      "signature": {
        "algorithm": "",
        "header": "",
        "use_param": false,
        "param_name": "",
        "secret": "",
        "allowed_clock_skew": 0,
        "error_code": 0,
        "error_message": ""
      }
    }
  },
  "use_basic_auth": false,
  "basic_auth": {
    "disable_caching": false,
    "cache_ttl": 0,
    "extract_from_body": false,
    "body_user_regexp": "",
    "body_password_regexp": ""
  },
  "use_mutual_tls_auth": false,
  "client_certificates": null,
  "upstream_certificates": null,
  "pinned_public_keys": null,
  "enable_jwt": true,
  "use_standard_auth": false,
  "use_go_plugin_auth": false,
  "enable_coprocess_auth": false,
  "jwt_signing_method": "",
  "jwt_source": "",
  "jwt_identity_base_field": "",
  "jwt_client_base_field": "",
  "jwt_policy_field_name": "",
  "jwt_default_policies": [ ],
  "jwt_issued_at_validation_skew": 0,
  "jwt_expires_at_validation_skew": 0,
  "jwt_not_before_validation_skew": 0,
  "jwt_skip_kid": false,
  "scopes": {
    "jwt": {
      "scope_claim_name": "",
      "scope_to_policy": null
    },
    "oidc": {
      "scope_claim_name": "",
      "scope_to_policy": null
    }
  },
  "jwt_scope_to_policy_mapping": {},
  "jwt_scope_claim_name": "",
  "notifications": {
    "shared_secret": "",
    "oauth_on_keychange_url": ""
  },
  "enable_signature_checking": false,
  "hmac_allowed_clock_skew": 0,
  "hmac_allowed_algorithms": null,
  "request_signing": {
    "is_enabled": false,
    "secret": "",
    "key_id": "",
    "algorithm": "",
    "header_list": null,
    "certificate_id": "",
    "signature_header": ""
  },
  "base_identity_provided_by": "",
  "definition": {
    "enabled": false,
    "name": "",
    "default": "",
    "location": "",
    "key": "",
    "strip_path": false,
    "strip_versioning_data": false,
    "versions": null
  },
  "version_data": {
    "not_versioned": true,
    "default_version": "",
    "versions": {
      "Default": {
        "name": "Default",
        "expires": "",
        "paths": {
          "ignored": null,
          "white_list": null,
          "black_list": null
        },
        "use_extended_paths": true,
        "extended_paths": {},
        "global_headers": null,
        "global_headers_remove": null,
        "global_response_headers": null,
        "global_response_headers_remove": null,
        "ignore_endpoint_case": false,
        "global_size_limit": 0,
        "override_target": ""
      }
    }
  },
  "uptime_tests": {
    "check_list": null,
    "config": {
      "expire_utime_after": 0,
      "service_discovery": {
        "use_discovery_service": false,
        "query_endpoint": "",
        "use_nested_query": false,
        "parent_data_path": "",
        "data_path": "",
        "port_data_path": "",
        "target_path": "",
        "use_target_list": false,
        "cache_timeout": 0,
        "endpoint_returns_list": false
      },
      "recheck_wait": 0
    }
  },
  "proxy": {
    "preserve_host_header": false,
    "listen_path": "/service1/",
    "target_url": "http://service1/graphql",
    "disable_strip_slash": false,
    "strip_listen_path": true,
    "enable_load_balancing": false,
    "target_list": null,
    "check_host_against_uptime_tests": false,
    "service_discovery": {
      "use_discovery_service": false,
      "query_endpoint": "",
      "use_nested_query": false,
      "parent_data_path": "",
      "data_path": "",
      "port_data_path": "",
      "target_path": "",
      "use_target_list": false,
      "cache_timeout": 0,
      "endpoint_returns_list": false
    },
    "transport": {
      "ssl_insecure_skip_verify": false,
      "ssl_ciphers": null,
      "ssl_min_version": 0,
      "ssl_max_version": 0,
      "ssl_force_common_name_check": false,
      "proxy_url": ""
    }
  },
  "disable_rate_limit": false,
  "disable_quota": false,
  "custom_middleware": {
    "pre": null,
    "post": null,
    "post_key_auth": null,
    "auth_check": {
      "name": "",
      "path": "",
      "require_session": false,
      "raw_body_only": false
    },
    "response": null,
    "driver": "python",
    "id_extractor": {
      "extract_from": "",
      "extract_with": "",
      "extractor_config": null
    }
  },
  "cache_options": {
    "cache_timeout": 0,
    "enable_cache": false,
    "cache_all_safe_requests": false,
    "cache_response_codes": null,
    "enable_upstream_cache_control": false,
    "cache_control_ttl_header": "",
    "cache_by_headers": null
  },
  "session_lifetime": 0,
  "active": true,
  "internal": true,
  "auth_provider": {
    "name": "",
    "storage_engine": "",
    "meta": null
  },
  "session_provider": {
    "name": "",
    "storage_engine": "",
    "meta": null
  },
  "event_handlers": {
    "events": null
  },
  "enable_batch_request_support": false,
  "enable_ip_whitelisting": false,
  "allowed_ips": null,
  "enable_ip_blacklisting": false,
  "blacklisted_ips": null,
  "dont_set_quota_on_create": false,
  "expire_analytics_after": 0,
  "response_processors": null,
  "CORS": {
    "enable": true,
    "allowed_origins": [
      "*"
    ],
    "allowed_methods": [
      "POST"
    ],
    "allowed_headers": [
      "Origin",
      "Content-Type",
      "Accept",
      "Authorization"
    ],
    "exposed_headers": [],
    "allow_credentials": false,
    "max_age": 3600,
    "options_passthrough": false,
    "debug": false
  },
  "domain": "",
  "certificates": null,
  "do_not_track": false,
  "tags": null,
  "enable_context_vars": false,
  "config_data": null,
  "tag_headers": null,
  "global_rate_limit": {
    "rate": 0,
    "per": 0
  },
  "strip_auth_data": false,
  "enable_detailed_recording": false,
  "graphql": {
    "enabled": true,
    "execution_mode": "subgraph",
    "version": "2",
    "schema": "type MyType {id1 ID!\n id2 ID!\n}\ntype Query {myTypes: [MyType!]}",
    "type_field_configurations": null,
    "playground": {
      "enabled": false,
      "path": ""
    },
    "engine": {
      "field_configs": null,
      "data_sources": null
    },
    "proxy": {
      "auth_headers": null
    },
    "subgraph": {
      "sdl": ""
    },
    "supergraph": null
  }
}

Subgraph 2:

{
  "name": "GraphQL API 2",
  "expiration": "2099-12-31 23:59",
  "slug": "",
  "listen_port": 0,
  "protocol": "",
  "enable_proxy_protocol": false,
  "api_id": "service2",
  "org_id": "",
  "use_keyless": true,
  "use_oauth2": false,
  "use_openid": false,
  "openid_options": {
    "providers": null,
    "segregate_by_client": false
  },
  "oauth_meta": {
    "allowed_access_types": null,
    "allowed_authorize_types": null,
    "auth_login_redirect": ""
  },
  "auth": {
    "name": "",
    "use_param": false,
    "param_name": "",
    "use_cookie": false,
    "cookie_name": "",
    "disable_header": false,
    "auth_header_name": "",
    "use_certificate": false,
    "validate_signature": false,
    "signature": {
      "algorithm": "",
      "header": "",
      "use_param": false,
      "param_name": "",
      "secret": "",
      "allowed_clock_skew": 0,
      "error_code": 0,
      "error_message": ""
    }
  },
  "auth_configs": {
    "authToken": {
      "name": "",
      "use_param": false,
      "param_name": "",
      "use_cookie": false,
      "cookie_name": "",
      "disable_header": false,
      "auth_header_name": "Authorization",
      "use_certificate": false,
      "validate_signature": false,
      "signature": {
        "algorithm": "",
        "header": "",
        "use_param": false,
        "param_name": "",
        "secret": "",
        "allowed_clock_skew": 0,
        "error_code": 0,
        "error_message": ""
      }
    }
  },
  "use_basic_auth": false,
  "basic_auth": {
    "disable_caching": false,
    "cache_ttl": 0,
    "extract_from_body": false,
    "body_user_regexp": "",
    "body_password_regexp": ""
  },
  "use_mutual_tls_auth": false,
  "client_certificates": null,
  "upstream_certificates": null,
  "pinned_public_keys": null,
  "enable_jwt": true,
  "use_standard_auth": false,
  "use_go_plugin_auth": false,
  "enable_coprocess_auth": false,
  "jwt_signing_method": "",
  "jwt_source": "",
  "jwt_identity_base_field": "",
  "jwt_client_base_field": "",
  "jwt_policy_field_name": "",
  "jwt_default_policies": [ ],
  "jwt_issued_at_validation_skew": 0,
  "jwt_expires_at_validation_skew": 0,
  "jwt_not_before_validation_skew": 0,
  "jwt_skip_kid": false,
  "scopes": {
    "jwt": {
      "scope_claim_name": "",
      "scope_to_policy": null
    },
    "oidc": {
      "scope_claim_name": "",
      "scope_to_policy": null
    }
  },
  "jwt_scope_to_policy_mapping": {},
  "jwt_scope_claim_name": "",
  "notifications": {
    "shared_secret": "",
    "oauth_on_keychange_url": ""
  },
  "enable_signature_checking": false,
  "hmac_allowed_clock_skew": 0,
  "hmac_allowed_algorithms": null,
  "request_signing": {
    "is_enabled": false,
    "secret": "",
    "key_id": "",
    "algorithm": "",
    "header_list": null,
    "certificate_id": "",
    "signature_header": ""
  },
  "base_identity_provided_by": "",
  "definition": {
    "enabled": false,
    "name": "",
    "default": "",
    "location": "",
    "key": "",
    "strip_path": false,
    "strip_versioning_data": false,
    "versions": null
  },
  "version_data": {
    "not_versioned": true,
    "default_version": "",
    "versions": {
      "Default": {
        "name": "Default",
        "expires": "",
        "paths": {
          "ignored": null,
          "white_list": null,
          "black_list": null
        },
        "use_extended_paths": true,
        "extended_paths": {},
        "global_headers": null,
        "global_headers_remove": null,
        "global_response_headers": null,
        "global_response_headers_remove": null,
        "ignore_endpoint_case": false,
        "global_size_limit": 0,
        "override_target": ""
      }
    }
  },
  "uptime_tests": {
    "check_list": null,
    "config": {
      "expire_utime_after": 0,
      "service_discovery": {
        "use_discovery_service": false,
        "query_endpoint": "",
        "use_nested_query": false,
        "parent_data_path": "",
        "data_path": "",
        "port_data_path": "",
        "target_path": "",
        "use_target_list": false,
        "cache_timeout": 0,
        "endpoint_returns_list": false
      },
      "recheck_wait": 0
    }
  },
  "proxy": {
    "preserve_host_header": false,
    "listen_path": "/service2/",
    "target_url": "http://service2/graphql",
    "disable_strip_slash": false,
    "strip_listen_path": true,
    "enable_load_balancing": false,
    "target_list": null,
    "check_host_against_uptime_tests": false,
    "service_discovery": {
      "use_discovery_service": false,
      "query_endpoint": "",
      "use_nested_query": false,
      "parent_data_path": "",
      "data_path": "",
      "port_data_path": "",
      "target_path": "",
      "use_target_list": false,
      "cache_timeout": 0,
      "endpoint_returns_list": false
    },
    "transport": {
      "ssl_insecure_skip_verify": false,
      "ssl_ciphers": null,
      "ssl_min_version": 0,
      "ssl_max_version": 0,
      "ssl_force_common_name_check": false,
      "proxy_url": ""
    }
  },
  "disable_rate_limit": false,
  "disable_quota": false,
  "custom_middleware": {
    "pre": null,
    "post": null,
    "post_key_auth": null,
    "auth_check": {
      "name": "",
      "path": "",
      "require_session": false,
      "raw_body_only": false
    },
    "response": null,
    "driver": "python",
    "id_extractor": {
      "extract_from": "",
      "extract_with": "",
      "extractor_config": null
    }
  },
  "cache_options": {
    "cache_timeout": 0,
    "enable_cache": false,
    "cache_all_safe_requests": false,
    "cache_response_codes": null,
    "enable_upstream_cache_control": false,
    "cache_control_ttl_header": "",
    "cache_by_headers": null
  },
  "session_lifetime": 0,
  "active": true,
  "internal": true,
  "auth_provider": {
    "name": "",
    "storage_engine": "",
    "meta": null
  },
  "session_provider": {
    "name": "",
    "storage_engine": "",
    "meta": null
  },
  "event_handlers": {
    "events": null
  },
  "enable_batch_request_support": false,
  "enable_ip_whitelisting": false,
  "allowed_ips": null,
  "enable_ip_blacklisting": false,
  "blacklisted_ips": null,
  "dont_set_quota_on_create": false,
  "expire_analytics_after": 0,
  "response_processors": null,
  "CORS": {
    "enable": true,
    "allowed_origins": [
      "*"
    ],
    "allowed_methods": [
      "POST"
    ],
    "allowed_headers": [
      "Origin",
      "Content-Type",
      "Accept",
      "Authorization"
    ],
    "exposed_headers": [],
    "allow_credentials": false,
    "max_age": 3600,
    "options_passthrough": false,
    "debug": false
  },
  "domain": "",
  "certificates": null,
  "do_not_track": false,
  "tags": null,
  "enable_context_vars": false,
  "config_data": null,
  "tag_headers": null,
  "global_rate_limit": {
    "rate": 0,
    "per": 0
  },
  "strip_auth_data": false,
  "enable_detailed_recording": false,
  "graphql": {
    "enabled": true,
    "execution_mode": "subgraph",
    "version": "2",
    "schema": "type MyType @key(fields: "id1 id2") @extends {\n  id1: ID! @external\n  id2: ID! @external\n  addedField: String\n}",
    "type_field_configurations": null,
    "playground": {
      "enabled": false,
      "path": ""
    },
    "engine": {
      "field_configs": null,
      "data_sources": null
    },
    "proxy": {
      "auth_headers": null
    },
    "subgraph": {
      "sdl": ""
    },
    "supergraph": null
  }
}
1 Like

Hey @Andrey_Nado

let me replicate it and also reach out to the dev team for details on how “disable_query_batching” works exactly. I’ll come back to you.

2 Likes

Hi @Andrey_Nado,

I see some problems with your subgraphs.

You’ve not declared the base entity (in Subgraph1) as an entity. MyType on Subgraph1 would need a @key directive for each primary key you wish to declare. I believe declaring more than one field in a key directive creates a compound key, which Tyk does not currently support.

If you want to reference an entity (not extend with fields), you would declare it as a stub. The stub would contain the minimal amount of information to identify the entity (referencing exactly ONE of the primary keys declared on the base entity). For example:

extend type MyType @key(fields: "id1") {
    id1: ID! @external
}

If you do want to extend the entity, you would do the same as above but with your new field. For example:

extend type MyType @key(fields: "id1") {
    id1: ID! @external
    addedField: String
}
1 Like

Hi @DavidS!

I have no problems with API federation. It works for me well including the composite keys.

The problem is an efficiency of requests to a secondary subgraph. Tyk issues one request per entity and it is quite slow.

Can I make Tyk to issue a single request with multiple entity representations?

Hi again,

I understand that there appear to be no current issues with federation currently, but what you have supplied as an example would be invalid under v1 rules. Your examples would be explicitly invalid in the upcoming Tyk release.

Regarding the actual issue; and I realise I haven’t really addressed it: I am not sure I can suggest much until the format is what is expected by v1. I think we must make sure everything is completely valid before we try to debug or offer advice.

Lastly, composite keys are still not supported, and the inefficiency may well be a product of that.

Hi @DavidS

I prepared a demo project with a simple API federation: GitHub - andrey-nakin/tyk-issues at batching-federation (notice that the code in batching-federation branch, not in main one).

I’m using a sample https://countries.trevorblades.com/graphql API as a base. This API returns list of countries. The corresponding GraphQL schema is:

type Query {
  countries: [Country!]!
}

type Country {
  code: String!
  name: String!
}

Then I’m extending this API by adding a new comment field in my own small backend application. The schema modification is the following:

extend type Country @key(fields: "code") {
  code: String! @external
  comment: String
}

I tried to prepare API definitions according to your comments above. Also I used this demo as a template: tyk-demo/deployments/federation at master · TykTechnologies/tyk-demo · GitHub

Then I run the following GraphQL query where the comment field is fetched from my custom backend:

{
  countries {
    code
    name
    comment
  }
}

One can see from the Tyk logs, entities are requested one by one, for example:

time="Jul 09 09:23:23" level=debug msg="Outbound request URL: http://gqlapi:8082/graphql" api_id=addition-service api_name="Addition GraphQL API" mw=ReverseProxy org_id=
time="Jul 09 09:23:23" level=debug msg="/data (beforeFetchHook): {"method":"POST","url":"http://gqlapi:8082/graphql","header":{},"body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on Country {comment}}}","variables":{"representations":[{"code":"ZM","__typename":"Country"}]}}}" api_id=addition-service api_name="Addition GraphQL API" mw=GraphQLMiddleware org_id= origin= path=[47 100 97 116 97]
time="Jul 09 09:23:23" level=debug msg="/data (afterFetchHook.OnData): {"_entities":[{"__typename":"Country","comment":"This is a sample comment for country ZM"}]}" api_id=addition-service api_name="Addition GraphQL API" mw=GraphQLMiddleware org_id= origin= path=[47 100 97 116 97] single_flight=false

Is my federation setup correct?

Hi,

Again, even though it’s likely not going to change the outcome, the subgraphs are invalid. The base entity MUST be declared as an entity. This means the subgraph that defines Country must also provide all the possible primary keys in @key directives.

In the upcoming release, you would be unable to build your federated supergraph with what you have supplied.

1 Like

Hi @DavidS
Thank you for the note, I fixed my demo project accordingly.
Can the primary issue (batching entity requests) be addressed?

Hi,

Can you confirm that with the valid subgraphs, the problem you described persists?

Yes, just checked it again. Entities are requested one by one. If main subgraph (countries.json) returns N entities, then Tyk issues N requests to the second subgraph (addition.json).

Hi,

Thanks for confirming. OK. This will require more investigation on our end.

Could you let me know what happens if you provide a stub (no additional fields)?

@DavidS,

if I remove comment field from the schema and then run countries query, Tyk simply issues a single request to the primary subgraph. The secondary subgraph is not called.

Hi,

This is helpful information; thank you. I’ll reply here to update you on the investigation (but it probably won’t be this week).

Hi Andrey,

I’m investigating the issue. I thought I’d found something, but I was mistaken. Apologies.

1 Like

@Andrey_Nado in case this doesn’t reach you otherwise.