diff --git a/ops/model.py b/ops/model.py
index b4940d2..9682548 100644
--- a/ops/model.py
+++ b/ops/model.py
@@ -28,7 +28,7 @@ import typing
 import weakref
 from abc import ABC, abstractmethod
 from pathlib import Path
-from subprocess import PIPE, CalledProcessError, run
+from subprocess import PIPE, CalledProcessError, CompletedProcess, run
 from typing import (
     Any,
     BinaryIO,
@@ -2025,9 +2025,12 @@ class _ModelBackend:
         self._leader_check_time = None
         self._hook_is_running = ''
 
-    def _run(self, *args: str, return_output: bool = False, use_json: bool = False
+    def _run(self, *args: str, return_output: bool = False,
+             use_json: bool = False, input_stream: Optional[bytes] = None
              ) -> Union[str, 'JsonObject', None]:
-        kwargs = dict(stdout=PIPE, stderr=PIPE, check=True)
+        kwargs = dict(stdout=PIPE, stderr=PIPE, check=True)  # type: Dict[str, Any]
+        if input_stream:
+            kwargs.update({"input": input_stream})
         which_cmd = shutil.which(args[0])
         if which_cmd is None:
             raise RuntimeError('command not found: {}'.format(args[0]))
@@ -2036,6 +2039,10 @@ class _ModelBackend:
             args += ('--format=json',)
         try:
             result = run(args, **kwargs)
+
+            # pyright infers the first match when argument overloading/unpacking is used,
+            # so this needs to be coerced into the right type
+            result = typing.cast('CompletedProcess[bytes]', result)
         except CalledProcessError as e:
             raise ModelError(e.stderr)
         if return_output:
@@ -2133,12 +2140,14 @@ class _ModelBackend:
                 raise RuntimeError(
                     'setting application data is not supported on Juju version {}'.format(version))
 
-        args = ['relation-set', '-r', str(relation_id), '{}={}'.format(key, value)]
+        args = ['relation-set', '-r', str(relation_id)]
         if is_app:
             args.append('--app')
+        args.extend(["--file", "-"])
 
         try:
-            return self._run(*args)
+            content = yaml.safe_dump({key: value}, encoding='utf8')  # type: ignore
+            return self._run(*args, input_stream=content)
         except ModelError as e:
             if self._is_relation_not_found(e):
                 raise RelationNotFoundError() from e

diff --git a/test/test_model.py b/test/test_model.py
index 838f653..0d38eed 100755
--- a/test/test_model.py
+++ b/test/test_model.py
@@ -1969,17 +1969,17 @@ class TestModelBackend(unittest.TestCase):
             lambda: fake_script(self, 'relation-set', 'echo fooerror >&2 ; exit 1'),
             lambda: self.backend.relation_set(3, 'foo', 'bar', is_app=False),
             ops.model.ModelError,
-            [['relation-set', '-r', '3', 'foo=bar']],
+            [['relation-set', '-r', '3', '--file', '-']],
         ), (
             lambda: fake_script(self, 'relation-set', 'echo {} >&2 ; exit 2'.format(err_msg)),
             lambda: self.backend.relation_set(3, 'foo', 'bar', is_app=False),
             ops.model.RelationNotFoundError,
-            [['relation-set', '-r', '3', 'foo=bar']],
+            [['relation-set', '-r', '3', '--file', '-']],
         ), (
             lambda: None,
             lambda: self.backend.relation_set(3, 'foo', 'bar', is_app=True),
             ops.model.RelationNotFoundError,
-            [['relation-set', '-r', '3', 'foo=bar', '--app']],
+            [['relation-set', '-r', '3', '--app', '--file', '-']],
         ), (
             lambda: fake_script(self, 'relation-get', 'echo fooerror >&2 ; exit 1'),
             lambda: self.backend.relation_get(3, 'remote/0', is_app=False),
@@ -2027,15 +2027,25 @@ class TestModelBackend(unittest.TestCase):
     def test_relation_set_juju_version_quirks(self):
         self.addCleanup(os.environ.pop, 'JUJU_VERSION', None)
 
-        fake_script(self, 'relation-set', 'exit 0')
-
         # on 2.7.0+, things proceed as expected
         for v in ['2.8.0', '2.7.0']:
             with self.subTest(v):
-                os.environ['JUJU_VERSION'] = v
-                self.backend.relation_set(1, 'foo', 'bar', is_app=True)
-                calls = [' '.join(i) for i in fake_script_calls(self, clear=True)]
-                self.assertEqual(calls, ['relation-set -r 1 foo=bar --app'])
+                t = tempfile.NamedTemporaryFile()
+                try:
+                    fake_script(self, 'relation-set', dedent("""
+                        cat >> {}
+                        """).format(pathlib.Path(t.name).as_posix()))
+                    os.environ['JUJU_VERSION'] = v
+                    self.backend.relation_set(1, 'foo', 'bar', is_app=True)
+                    calls = [' '.join(i) for i in fake_script_calls(self, clear=True)]
+                    self.assertEqual(calls, ['relation-set -r 1 --app --file -'])
+                    t.seek(0)
+                    content = t.read()
+                finally:
+                    t.close()
+                self.assertEqual(content.decode('utf-8'), dedent("""\
+                    foo: bar
+                    """))
 
         # before 2.7.0, it just fails always (no --app support)
         os.environ['JUJU_VERSION'] = '2.6.9'

`RelationDataContent.__setitem__` should dynamically dispatch to a file if it's too long
We've already seen this with Grafana Dashboards, which routinely overflow the maximum argument length from `subprocess`, but it was also observed that relating Prometheus to a very large number of targets could overflow and cause a strange looking `OSError` on a `RelationChangedEvent`

Ultimately, this is due to [`relation_set`](https://github.com/canonical/operator/blob/0a097748299506c7651bdef99524638146f9724a/ops/model.py#L951) calling back to `subprocess` to handle [`relation-set ...`](https://github.com/canonical/operator/blob/0a097748299506c7651bdef99524638146f9724a/ops/model.py#L2136). 

We already [split long log messages](https://github.com/canonical/operator/pull/632), and `relation-set` takes a `--file` parameter which reads in YAML, allowing the limit to be bypassed. If OF determines that the length of the relation data is anywhere near the limit, we could defer to something like:

```python
with tempfile.TempFile() as relation_data:
    with open(relation_data, "w") as f:
        f.write(yaml.dump({key: value})
    self._backend.relation_set(..., data_file=relation_data)
```

If an optarg were added to `relation_set` where, if present, data was loaded from a file. This seems easy enough to add, avoids requiring charm authors to carefully think about the size/length of their data bags and potentially destructure them to avoid it mapping back to a `map[string]string` on the backend, and yields the desired behavior.
test/test_model.py::TestModelBackend::test_relation_set_juju_version_quirks test/test_model.py::TestModelBackend::test_relation_tool_errors
test/test_model.py::TestModel::test_active_message_default test/test_model.py::TestModel::test_app_immutable test/test_model.py::TestModel::test_app_relation_data_modify_local_as_leader test/test_model.py::TestModel::test_app_relation_data_modify_local_as_minion test/test_model.py::TestModel::test_base_status_instance_raises test/test_model.py::TestModel::test_config test/test_model.py::TestModel::test_config_immutable test/test_model.py::TestModel::test_get_relation test/test_model.py::TestModel::test_invalid_type_relation_data test/test_model.py::TestModel::test_is_leader test/test_model.py::TestModel::test_local_set_valid_app_status test/test_model.py::TestModel::test_local_set_valid_unit_status test/test_model.py::TestModel::test_model_attributes test/test_model.py::TestModel::test_model_name_from_backend test/test_model.py::TestModel::test_our_unit_is_our test/test_model.py::TestModel::test_peer_relation_app test/test_model.py::TestModel::test_pod_immutable test/test_model.py::TestModel::test_pod_spec test/test_model.py::TestModel::test_relation_data_del_key test/test_model.py::TestModel::test_relation_data_del_missing_key test/test_model.py::TestModel::test_relation_data_modify_our test/test_model.py::TestModel::test_relation_data_modify_remote test/test_model.py::TestModel::test_relation_data_type_check test/test_model.py::TestModel::test_relation_no_units test/test_model.py::TestModel::test_relation_set_fail test/test_model.py::TestModel::test_relations_immutable test/test_model.py::TestModel::test_relations_keys test/test_model.py::TestModel::test_remote_app_relation_data test/test_model.py::TestModel::test_remote_app_status test/test_model.py::TestModel::test_remote_unit_status test/test_model.py::TestModel::test_remote_units_is_our test/test_model.py::TestModel::test_resources test/test_model.py::TestModel::test_resources_immutable test/test_model.py::TestModel::test_set_app_status_invalid test/test_model.py::TestModel::test_set_app_status_non_leader_raises test/test_model.py::TestModel::test_set_unit_status_invalid test/test_model.py::TestModel::test_status_eq test/test_model.py::TestModel::test_status_repr test/test_model.py::TestModel::test_storage test/test_model.py::TestModel::test_storages_immutable test/test_model.py::TestModel::test_unit_immutable test/test_model.py::TestModel::test_unit_relation_data test/test_model.py::TestModel::test_workload_version test/test_model.py::TestModel::test_workload_version_invalid test/test_model.py::test_recursive_list[case0] test/test_model.py::test_recursive_list[case1] test/test_model.py::test_recursive_list[case2] test/test_model.py::test_recursive_push_and_pull[case0] test/test_model.py::test_recursive_push_and_pull[case1] test/test_model.py::test_recursive_push_and_pull[case2] test/test_model.py::test_recursive_push_and_pull[case3] test/test_model.py::test_recursive_push_and_pull[case4] test/test_model.py::test_recursive_push_and_pull[case5] test/test_model.py::test_recursive_push_and_pull[case6] test/test_model.py::test_recursive_push_and_pull[case7] test/test_model.py::test_recursive_push_and_pull[case8] test/test_model.py::TestApplication::test_mocked_get_services test/test_model.py::TestApplication::test_planned_units test/test_model.py::TestApplication::test_planned_units_garbage_values test/test_model.py::TestApplication::test_planned_units_override test/test_model.py::TestApplication::test_planned_units_user_set test/test_model.py::TestContainers::test_unit_containers test/test_model.py::TestContainers::test_unit_get_container test/test_model.py::TestContainerPebble::test_add_layer test/test_model.py::TestContainerPebble::test_autostart test/test_model.py::TestContainerPebble::test_bare_can_connect_call test/test_model.py::TestContainerPebble::test_exec test/test_model.py::TestContainerPebble::test_get_check test/test_model.py::TestContainerPebble::test_get_checks test/test_model.py::TestContainerPebble::test_get_plan test/test_model.py::TestContainerPebble::test_get_service test/test_model.py::TestContainerPebble::test_get_services test/test_model.py::TestContainerPebble::test_get_system_info test/test_model.py::TestContainerPebble::test_list_files test/test_model.py::TestContainerPebble::test_make_dir test/test_model.py::TestContainerPebble::test_pull test/test_model.py::TestContainerPebble::test_push test/test_model.py::TestContainerPebble::test_remove_path test/test_model.py::TestContainerPebble::test_replan test/test_model.py::TestContainerPebble::test_restart test/test_model.py::TestContainerPebble::test_restart_fallback test/test_model.py::TestContainerPebble::test_restart_fallback_non_400_error test/test_model.py::TestContainerPebble::test_restart_no_arguments test/test_model.py::TestContainerPebble::test_send_signal test/test_model.py::TestContainerPebble::test_socket_path test/test_model.py::TestContainerPebble::test_start test/test_model.py::TestContainerPebble::test_start_no_arguments test/test_model.py::TestContainerPebble::test_stop test/test_model.py::TestContainerPebble::test_stop_no_arguments test/test_model.py::TestContainerPebble::test_type_errors test/test_model.py::TestModelBindings::test_binding_by_relation test/test_model.py::TestModelBindings::test_binding_by_relation_name test/test_model.py::TestModelBindings::test_binding_no_iface_name test/test_model.py::TestModelBindings::test_dead_relations test/test_model.py::TestModelBindings::test_empty_bind_addresses test/test_model.py::TestModelBindings::test_empty_interface_info test/test_model.py::TestModelBindings::test_invalid_keys test/test_model.py::TestModelBindings::test_missing_bind_addresses test/test_model.py::TestModelBindings::test_missing_egress_subnets test/test_model.py::TestModelBindings::test_missing_ingress_addresses test/test_model.py::TestModelBindings::test_no_bind_addresses test/test_model.py::TestModelBackend::test_action_fail test/test_model.py::TestModelBackend::test_action_get test/test_model.py::TestModelBackend::test_action_get_error test/test_model.py::TestModelBackend::test_action_log test/test_model.py::TestModelBackend::test_action_log_error test/test_model.py::TestModelBackend::test_action_set test/test_model.py::TestModelBackend::test_action_set_dotted_dict test/test_model.py::TestModelBackend::test_action_set_duplicated_keys test/test_model.py::TestModelBackend::test_action_set_error test/test_model.py::TestModelBackend::test_action_set_key_validation test/test_model.py::TestModelBackend::test_action_set_more_nested test/test_model.py::TestModelBackend::test_action_set_nested test/test_model.py::TestModelBackend::test_application_version_set test/test_model.py::TestModelBackend::test_application_version_set_invalid test/test_model.py::TestModelBackend::test_invalid_metric_label_values test/test_model.py::TestModelBackend::test_invalid_metric_labels test/test_model.py::TestModelBackend::test_invalid_metric_names test/test_model.py::TestModelBackend::test_invalid_metric_values test/test_model.py::TestModelBackend::test_is_leader_refresh test/test_model.py::TestModelBackend::test_juju_log test/test_model.py::TestModelBackend::test_local_set_invalid_status test/test_model.py::TestModelBackend::test_network_get test/test_model.py::TestModelBackend::test_network_get_errors test/test_model.py::TestModelBackend::test_relation_get_juju_version_quirks test/test_model.py::TestModelBackend::test_relation_get_set_is_app_arg test/test_model.py::TestModelBackend::test_relation_remote_app_name_env test/test_model.py::TestModelBackend::test_relation_remote_app_name_script_errors test/test_model.py::TestModelBackend::test_relation_remote_app_name_script_success test/test_model.py::TestModelBackend::test_status_get test/test_model.py::TestModelBackend::test_status_is_app_forced_kwargs test/test_model.py::TestModelBackend::test_status_set_is_app_not_bool_raises test/test_model.py::TestModelBackend::test_storage_tool_errors test/test_model.py::TestModelBackend::test_valid_metrics test/test_model.py::TestLazyMapping::test_invalidate