diff --git a/ParallelisationGuideExamples/README.md b/ParallelisationGuideExamples/README.md new file mode 100644 index 0000000..72c33ad --- /dev/null +++ b/ParallelisationGuideExamples/README.md @@ -0,0 +1,9 @@ +# sap-blog-article-di-parallelisation-guide-examples + +# Overview + +This repository is used to share example graphs for SAP Data Intelligence introducing multi-instancing and multi-processing parallelisation methods to pipeline developers It includes a ZIP-archive (so called Data Intelligence solution) which can be imported into the Data Intelligence cluster using the System Management application and an unarchived version of the solution to allow browsing the single graphs (files) directly on github. + +# Link to the official Guide + +You can find the technical blog article which describes the usage of the different parallelisation methods provided here at [this page](https://blogs.sap.com/?p=1489984). diff --git a/ParallelisationGuideExamples/multi-processing-examples-1.0.0.zip b/ParallelisationGuideExamples/multi-processing-examples-1.0.0.zip new file mode 100644 index 0000000..67122bd Binary files /dev/null and b/ParallelisationGuideExamples/multi-processing-examples-1.0.0.zip differ diff --git a/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/graphs/com/example/master-worker/graph.json b/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/graphs/com/example/master-worker/graph.json new file mode 100644 index 0000000..0f22345 --- /dev/null +++ b/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/graphs/com/example/master-worker/graph.json @@ -0,0 +1,142 @@ +{ + "properties": {}, + "description": "[Ex] Master-Worker Pattern", + "processes": { + "constantgenerator1": { + "component": "com.sap.util.constantGenerator", + "metadata": { + "label": "250ms Generator", + "x": 17, + "y": 12, + "height": 80, + "width": 120, + "extensible": true, + "generation": 1, + "config": { + "mode": "pulse", + "duration": "250ms" + } + } + }, + "python3operator1": { + "component": "com.sap.system.python3Operator", + "metadata": { + "label": "Capture Time (10)", + "x": 355, + "y": 12, + "height": 80, + "width": 120, + "extensible": true, + "filesRequired": [ + "script.py" + ], + "generation": 1, + "config": { + "script": "from datetime import datetime\nlast = datetime.now()\nn_inputs = 0\n\ndef on_input(data):\n global last\n global n_inputs\n n_inputs += 1\n \n if n_inputs == 10:\n now = datetime.now()\n diff = now - last\n last = now\n n_inputs = 0\n api.send(\"out\", str(diff))\n\napi.set_port_callback(\"in\", on_input)" + }, + "additionalinports": [ + { + "name": "in", + "type": "string" + } + ], + "additionaloutports": [ + { + "name": "out", + "type": "string" + } + ] + } + }, + "wiretap1": { + "component": "com.sap.util.wiretap", + "metadata": { + "label": "Wiretap", + "x": 524, + "y": 12, + "height": 80, + "width": 120, + "generation": 1, + "ui": "dynpath", + "config": {} + } + }, + "python3operator2": { + "component": "com.sap.system.python3Operator", + "metadata": { + "label": "1s ", + "x": 186, + "y": 12, + "height": 80, + "width": 120, + "extensible": true, + "filesRequired": [ + "script.py" + ], + "generation": 1, + "config": { + "script": "import multiprocessing\nfrom multiprocessing import Pool, get_context\nmultiprocessing.set_start_method('spawn')\n\nfrom operators.com.example.multi import parallel_fun\n\nq_in = multiprocessing.Queue(1)\nq_out = multiprocessing.Queue()\n\n# Spawn workers\nn_proc = 4\nproc = [multiprocessing.Process(target=parallel_fun, args=(q_in, q_out)) for _ in range(n_proc)]\nfor p in proc:\n p.daemon = True\n p.start()\n\n# Input callback sends data to in queue\ndef on_input(message):\n # Just put the Message into the queue for the workers\n q_in.put((False, message))\n \n# Timer callback is handling the results from the out queue\nimport queue\ndef t1():\n try:\n out = q_out.get()\n api.send(\"out\", out)\n except queue.Empty:\n pass\n \n# \"0\" timer callback is started as quickly as possible (basically a while loop)\n# Increase time if you expect the out_queue to be empty most of the time\napi.add_timer(\"0\", t1)\n \n# shutdown the workers\ndef shutdown_workers():\n for _ in range(n_proc):\n q_in.put((True, None))\n\napi.add_shutdown_handler(shutdown_workers)\n \napi.set_port_callback(\"in\", on_input)" + }, + "additionalinports": [ + { + "name": "in", + "type": "string" + } + ], + "additionaloutports": [ + { + "name": "out", + "type": "string" + } + ] + } + } + }, + "groups": [], + "connections": [ + { + "metadata": { + "points": "479,52 519,52" + }, + "src": { + "port": "out", + "process": "python3operator1" + }, + "tgt": { + "port": "in", + "process": "wiretap1" + } + }, + { + "metadata": { + "points": "141,52 181,52" + }, + "src": { + "port": "out", + "process": "constantgenerator1" + }, + "tgt": { + "port": "in", + "process": "python3operator2" + } + }, + { + "metadata": { + "points": "310,52 350,52" + }, + "src": { + "port": "out", + "process": "python3operator2" + }, + "tgt": { + "port": "in", + "process": "python3operator1" + } + } + ], + "inports": {}, + "outports": {}, + "metadata": { + "generation": 1 + } +} \ No newline at end of file diff --git a/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/graphs/com/example/multiplicity/graph.json b/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/graphs/com/example/multiplicity/graph.json new file mode 100644 index 0000000..868002a --- /dev/null +++ b/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/graphs/com/example/multiplicity/graph.json @@ -0,0 +1,153 @@ +{ + "properties": {}, + "description": "[Ex] Multiplicity / Multi-instancing", + "processes": { + "python3operator2": { + "component": "com.sap.system.python3Operator", + "metadata": { + "label": "1s ", + "x": 186, + "y": 12, + "height": 80, + "width": 120, + "extensible": true, + "filesRequired": [ + "script.py" + ], + "generation": 1, + "config": { + "script": "from datetime import datetime\nimport time\n\ndef on_input(data):\n time.sleep(1)\n api.send(\"out\", data)\n\napi.set_port_callback(\"in\", on_input)" + }, + "additionalinports": [ + { + "name": "in", + "type": "string" + } + ], + "additionaloutports": [ + { + "name": "out", + "type": "string" + } + ] + } + }, + "constantgenerator1": { + "component": "com.sap.util.constantGenerator", + "metadata": { + "label": "250ms Generator", + "x": 17, + "y": 12, + "height": 80, + "width": 120, + "extensible": true, + "generation": 1, + "config": { + "mode": "pulse", + "duration": "250ms" + } + } + }, + "python3operator1": { + "component": "com.sap.system.python3Operator", + "metadata": { + "label": "Capture Time (10)", + "x": 355, + "y": 12, + "height": 80, + "width": 120, + "extensible": true, + "filesRequired": [ + "script.py" + ], + "generation": 1, + "config": { + "script": "from datetime import datetime\nlast = datetime.now()\nn_inputs = 0\n\ndef on_input(data):\n global last\n global n_inputs\n n_inputs += 1\n \n if n_inputs == 10:\n now = datetime.now()\n diff = now - last\n last = now\n n_inputs = 0\n api.send(\"out\", str(diff))\n\napi.set_port_callback(\"in\", on_input)" + }, + "additionalinports": [ + { + "name": "in", + "type": "string" + } + ], + "additionaloutports": [ + { + "name": "out", + "type": "string" + } + ] + } + }, + "wiretap1": { + "component": "com.sap.util.wiretap", + "metadata": { + "label": "Wiretap", + "x": 524, + "y": 12, + "height": 80, + "width": 120, + "generation": 1, + "ui": "dynpath", + "config": {} + } + } + }, + "groups": [ + { + "name": "group1", + "nodes": [ + "python3operator2" + ], + "metadata": { + "description": "Group" + }, + "multiplicity": 4 + } + ], + "connections": [ + { + "metadata": { + "points": "479,52 519,52" + }, + "src": { + "port": "out", + "process": "python3operator1" + }, + "tgt": { + "port": "in", + "process": "wiretap1" + } + }, + { + "metadata": { + "points": "141,52 181,52" + }, + "src": { + "port": "out", + "process": "constantgenerator1" + }, + "tgt": { + "port": "in", + "process": "python3operator2" + } + }, + { + "metadata": { + "points": "310,52 350,52" + }, + "src": { + "port": "out", + "process": "python3operator2" + }, + "tgt": { + "port": "in", + "process": "python3operator1" + } + } + ], + "inports": {}, + "outports": {}, + "metadata": { + "generation": 1 + } +} diff --git a/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/graphs/com/example/original/graph.json b/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/graphs/com/example/original/graph.json new file mode 100644 index 0000000..6d050e6 --- /dev/null +++ b/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/graphs/com/example/original/graph.json @@ -0,0 +1,142 @@ +{ + "properties": {}, + "description": "[Ex] Original Graph", + "processes": { + "constantgenerator1": { + "component": "com.sap.util.constantGenerator", + "metadata": { + "label": "250ms Generator", + "x": 17, + "y": 12, + "height": 80, + "width": 120, + "extensible": true, + "generation": 1, + "config": { + "mode": "pulse", + "duration": "250ms" + } + } + }, + "python3operator1": { + "component": "com.sap.system.python3Operator", + "metadata": { + "label": "Capture Time (10)", + "x": 355, + "y": 12, + "height": 80, + "width": 120, + "extensible": true, + "filesRequired": [ + "script.py" + ], + "generation": 1, + "config": { + "script": "from datetime import datetime\nlast = datetime.now()\nn_inputs = 0\n\ndef on_input(data):\n global last\n global n_inputs\n n_inputs += 1\n \n if n_inputs == 10:\n now = datetime.now()\n diff = now - last\n last = now\n n_inputs = 0\n api.send(\"out\", str(diff))\n\napi.set_port_callback(\"in\", on_input)" + }, + "additionalinports": [ + { + "name": "in", + "type": "string" + } + ], + "additionaloutports": [ + { + "name": "out", + "type": "string" + } + ] + } + }, + "wiretap1": { + "component": "com.sap.util.wiretap", + "metadata": { + "label": "Wiretap", + "x": 524, + "y": 12, + "height": 80, + "width": 120, + "generation": 1, + "ui": "dynpath", + "config": {} + } + }, + "python3operator2": { + "component": "com.sap.system.python3Operator", + "metadata": { + "label": "1s ", + "x": 186, + "y": 12, + "height": 80, + "width": 120, + "extensible": true, + "filesRequired": [ + "script.py" + ], + "generation": 1, + "config": { + "script": "from datetime import datetime\nimport time\n\ndef on_input(data):\n time.sleep(1)\n api.send(\"out\", data)\n\napi.set_port_callback(\"in\", on_input)" + }, + "additionalinports": [ + { + "name": "in", + "type": "string" + } + ], + "additionaloutports": [ + { + "name": "out", + "type": "string" + } + ] + } + } + }, + "groups": [], + "connections": [ + { + "metadata": { + "points": "479,52 519,52" + }, + "src": { + "port": "out", + "process": "python3operator1" + }, + "tgt": { + "port": "in", + "process": "wiretap1" + } + }, + { + "metadata": { + "points": "141,52 181,52" + }, + "src": { + "port": "out", + "process": "constantgenerator1" + }, + "tgt": { + "port": "in", + "process": "python3operator2" + } + }, + { + "metadata": { + "points": "310,52 350,52" + }, + "src": { + "port": "out", + "process": "python3operator2" + }, + "tgt": { + "port": "in", + "process": "python3operator1" + } + } + ], + "inports": {}, + "outports": {}, + "metadata": { + "generation": 1 + } +} diff --git a/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/graphs/com/example/pools/graph.json b/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/graphs/com/example/pools/graph.json new file mode 100644 index 0000000..92e40aa --- /dev/null +++ b/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/graphs/com/example/pools/graph.json @@ -0,0 +1,185 @@ +{ + "properties": {}, + "description": "[Ex] Process Pools", + "processes": { + "constantgenerator1": { + "component": "com.sap.util.constantGenerator", + "metadata": { + "label": "250ms Generator", + "x": 17, + "y": 12, + "height": 80, + "width": 120, + "extensible": true, + "generation": 1, + "config": { + "mode": "pulse", + "duration": "250ms" + } + } + }, + "python3operator1": { + "component": "com.sap.system.python3Operator", + "metadata": { + "label": "Capture Time (10)", + "x": 524, + "y": 12, + "height": 80, + "width": 120, + "extensible": true, + "filesRequired": [ + "script.py" + ], + "generation": 1, + "config": { + "script": "from datetime import datetime\nlast = datetime.now()\nn_inputs = 0\n\ndef on_input(data):\n global last\n global n_inputs\n n_inputs += 1\n \n if n_inputs == 10:\n now = datetime.now()\n diff = now - last\n last = now\n n_inputs = 0\n api.send(\"out\", str(diff))\n\napi.set_port_callback(\"in\", on_input)" + }, + "additionalinports": [ + { + "name": "in", + "type": "string" + } + ], + "additionaloutports": [ + { + "name": "out", + "type": "string" + } + ] + } + }, + "wiretap1": { + "component": "com.sap.util.wiretap", + "metadata": { + "label": "Wiretap", + "x": 693, + "y": 12, + "height": 80, + "width": 120, + "generation": 1, + "ui": "dynpath", + "config": {} + } + }, + "python3operator2": { + "component": "com.sap.system.python3Operator", + "metadata": { + "label": "1s ", + "x": 355, + "y": 12, + "height": 80, + "width": 120, + "extensible": true, + "filesRequired": [ + "script.py" + ], + "generation": 1, + "config": { + "script": "from multiprocessing import Pool, get_context\nfrom operators.com.example.multi import parallel_fun3\n\n# Pool\ndef on_input(data):\n # the list of messages is in the input data's body\n messages = data.body\n api.logger.info(\"logs before Parallel start\")\n # Create a pool with 10 processes\n with get_context(\"spawn\").Pool(10) as pool:\n # Run parallel function and split the input among them\n results = pool.map(parallel_fun3, data.body)\n # Send out the results as single messages\n for result in results:\n api.send(\"out\", result)\n api.logger.info(\"logs after Parallel start\")\n\napi.set_port_callback(\"batch\", on_input)" + }, + "additionalinports": [ + { + "name": "batch", + "type": "message" + } + ], + "additionaloutports": [ + { + "name": "out", + "type": "string" + } + ] + } + }, + "python3operator3": { + "component": "com.sap.system.python3Operator", + "metadata": { + "label": "collect 10 messages", + "x": 186, + "y": 12, + "height": 80, + "width": 120, + "extensible": true, + "filesRequired": [ + "script.py" + ], + "generation": 1, + "config": { + "script": "messages = []\ndef on_input(message):\n global messages\n # Collect messages in a list\n messages.append(message)\n # Until we have 10 and send them out as a batch of 10 messages\n if len(messages) == 10:\n api.send(\"batch\", messages.copy())\n # Reset the variable for the next batch\n messages = []\n \napi.set_port_callback(\"in\", on_input)" + }, + "additionalinports": [ + { + "name": "in", + "type": "string" + } + ], + "additionaloutports": [ + { + "name": "batch", + "type": "message" + } + ] + } + } + }, + "groups": [], + "connections": [ + { + "metadata": { + "points": "648,52 688,52" + }, + "src": { + "port": "out", + "process": "python3operator1" + }, + "tgt": { + "port": "in", + "process": "wiretap1" + } + }, + { + "metadata": { + "points": "479,52 519,52" + }, + "src": { + "port": "out", + "process": "python3operator2" + }, + "tgt": { + "port": "in", + "process": "python3operator1" + } + }, + { + "metadata": { + "points": "310,52 350,52" + }, + "src": { + "port": "batch", + "process": "python3operator3" + }, + "tgt": { + "port": "batch", + "process": "python3operator2" + } + }, + { + "metadata": { + "points": "141,52 181,52" + }, + "src": { + "port": "out", + "process": "constantgenerator1" + }, + "tgt": { + "port": "in", + "process": "python3operator3" + } + } + ], + "inports": {}, + "outports": {}, + "metadata": { + "generation": 1 + } +} diff --git a/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/graphs/com/example/spawn-process/graph.json b/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/graphs/com/example/spawn-process/graph.json new file mode 100644 index 0000000..10f978b --- /dev/null +++ b/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/graphs/com/example/spawn-process/graph.json @@ -0,0 +1,142 @@ +{ + "properties": {}, + "description": "[Ex] Spawning Daemon Processes", + "processes": { + "constantgenerator1": { + "component": "com.sap.util.constantGenerator", + "metadata": { + "label": "250ms Generator", + "x": 17, + "y": 12, + "height": 80, + "width": 120, + "extensible": true, + "generation": 1, + "config": { + "mode": "pulse", + "duration": "250ms" + } + } + }, + "python3operator1": { + "component": "com.sap.system.python3Operator", + "metadata": { + "label": "Capture Time (10)", + "x": 355, + "y": 12, + "height": 80, + "width": 120, + "extensible": true, + "filesRequired": [ + "script.py" + ], + "generation": 1, + "config": { + "script": "from datetime import datetime\nlast = datetime.now()\nn_inputs = 0\n\ndef on_input(data):\n global last\n global n_inputs\n n_inputs += 1\n \n if n_inputs == 10:\n now = datetime.now()\n diff = now - last\n last = now\n n_inputs = 0\n api.send(\"out\", str(diff))\n\napi.set_port_callback(\"in\", on_input)" + }, + "additionalinports": [ + { + "name": "in", + "type": "string" + } + ], + "additionaloutports": [ + { + "name": "out", + "type": "string" + } + ] + } + }, + "wiretap1": { + "component": "com.sap.util.wiretap", + "metadata": { + "label": "Wiretap", + "x": 524, + "y": 12, + "height": 80, + "width": 120, + "generation": 1, + "ui": "dynpath", + "config": {} + } + }, + "python3operator2": { + "component": "com.sap.system.python3Operator", + "metadata": { + "label": "1s ", + "x": 186, + "y": 12, + "height": 80, + "width": 120, + "extensible": true, + "filesRequired": [ + "script.py" + ], + "generation": 1, + "config": { + "script": "from multiprocessing import Pool, get_context\nfrom operators.com.example.multi import parallel_fun2\n\nimport multiprocessing as mp\nmp.set_start_method('spawn')\nq = mp.Queue()\n# Process\ndef on_input(message):\n # Only spawn the process here\n # Dont get the data from the queue. Otherwhise your main process will be blocked again\n # Also don't join daemon processes.\n api.logger.info(\"logs before Parallel start\")\n p = mp.Process(target=parallel_fun2, args=(message, q))\n p.daemon = True\n p.start()\n api.logger.info(\"logs after Parallel start\")\n \n \n# Timer callback is handling the results from the out queue\nimport queue\ndef t1():\n try:\n result = q.get()\n api.send(\"out\", result)\n except queue.Empty:\n pass\n \n# \"0\" timer callback is started as quickly as possible (basically a while loop)\n# Increase time if you expect the out_queue to be empty most of the time\napi.add_timer(\"0\", t1)\n \napi.set_port_callback(\"in\", on_input)" + }, + "additionalinports": [ + { + "name": "in", + "type": "string" + } + ], + "additionaloutports": [ + { + "name": "out", + "type": "string" + } + ] + } + } + }, + "groups": [], + "connections": [ + { + "metadata": { + "points": "479,52 519,52" + }, + "src": { + "port": "out", + "process": "python3operator1" + }, + "tgt": { + "port": "in", + "process": "wiretap1" + } + }, + { + "metadata": { + "points": "141,52 181,52" + }, + "src": { + "port": "out", + "process": "constantgenerator1" + }, + "tgt": { + "port": "in", + "process": "python3operator2" + } + }, + { + "metadata": { + "points": "310,52 350,52" + }, + "src": { + "port": "out", + "process": "python3operator2" + }, + "tgt": { + "port": "in", + "process": "python3operator1" + } + } + ], + "inports": {}, + "outports": {}, + "metadata": { + "generation": 1 + } +} diff --git a/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/subengines/com/sap/python36/operators/com/example/__init__.py b/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/subengines/com/sap/python36/operators/com/example/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/subengines/com/sap/python36/operators/com/example/__pycache__/__init__.cpython-39.pyc b/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/subengines/com/sap/python36/operators/com/example/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..f2ae9a3 Binary files /dev/null and b/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/subengines/com/sap/python36/operators/com/example/__pycache__/__init__.cpython-39.pyc differ diff --git a/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/subengines/com/sap/python36/operators/com/example/__pycache__/multi.cpython-39.pyc b/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/subengines/com/sap/python36/operators/com/example/__pycache__/multi.cpython-39.pyc new file mode 100644 index 0000000..a815989 Binary files /dev/null and b/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/subengines/com/sap/python36/operators/com/example/__pycache__/multi.cpython-39.pyc differ diff --git a/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/subengines/com/sap/python36/operators/com/example/multi.py b/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/subengines/com/sap/python36/operators/com/example/multi.py new file mode 100644 index 0000000..dee3f70 --- /dev/null +++ b/ParallelisationGuideExamples/multi-processing-examples-1.0.0/content/files/vflow/subengines/com/sap/python36/operators/com/example/multi.py @@ -0,0 +1,23 @@ +import time + + +# Used for Master-Slave Pattern +def parallel_fun(q_in, q_out): + while True: + shutdown, x = q_in.get() + if shutdown is True: + break + time.sleep(1) + q_out.put(("done")) + + +# Used for spawned Daemon Processes Example +def parallel_fun2(message, q_out): + time.sleep(1) + q_out.put((message)) + + +# Used for Process Pools Example +def parallel_fun3(message): + time.sleep(1) + return message \ No newline at end of file diff --git a/ParallelisationGuideExamples/multi-processing-examples-1.0.0/manifest.json b/ParallelisationGuideExamples/multi-processing-examples-1.0.0/manifest.json new file mode 100755 index 0000000..097a0bf --- /dev/null +++ b/ParallelisationGuideExamples/multi-processing-examples-1.0.0/manifest.json @@ -0,0 +1 @@ +{"name":"multi-processing-examples","version":"1.0.0","format":"2","dependencies":[],"description":"Examples showing different parallelisation methods"} \ No newline at end of file diff --git a/README.md b/README.md index 4012807..374b341 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ For details on how to configure and run the examples after the solution has been | [JavaProcessExecutor](/JavaProcessExecutor) | Run a Java Application using a Process Executor Operator | | [ParquetWriterOperator](/ParquetWriterOperator) | This custom operator creates a file in Parquet format from an input message | | [Qualtrics Survey integration](/QualtricsIntegration) | Custom operators to extract survey responses from Qualtrics | +| [Parallelisation Method Examples](/ParallelisationGuideExamples) | Example Graphs showing different possible parallelisation methods in SAP Data Intelligence | ## Known Issues