{"id":434,"date":"2024-09-24T14:25:40","date_gmt":"2024-09-24T14:25:40","guid":{"rendered":"https:\/\/niklas-boesch.de\/?p=434"},"modified":"2024-09-25T13:17:25","modified_gmt":"2024-09-25T13:17:25","slug":"import-10000-records-in-under-2-minutes-using-createmultiple-in-power-automate","status":"publish","type":"post","link":"https:\/\/niklas-boesch.de\/en\/2024\/09\/import-10000-records-in-under-2-minutes-using-createmultiple-in-power-automate","title":{"rendered":"Import 10,000 records in under 2 minutes &#8211; using CreateMultiple in Power Automate"},"content":{"rendered":"\n<p>Importing large datasets in Power Platform can be frustratingly slow, especially during supposedly one-time migrations for which you don&#8217;t want to license or use a paid integration platform. In one of my projects, constant changes to the data model turned a seemingly simple import of data from a legacy system into a days-long headache.<br><br>Quite a few times, we had to delete the data we had in our testing environment and start a new import. With more than 100,000 records (a comparatively small number for a legacy system), this still took a few hours each time. We had a cycle of &#8222;here&#8217;s a problem&#8220; &#8211; &#8222;alright, please check tomorrow&#8220;, frustrating both the team testing for data quality and myself.<\/p>\n\n\n\n<p>The recently introduced Bulk Messages &#8222;Create Multiple&#8220;, &#8222;Update Multiple&#8220;, &#8222;Upsert Multiple&#8220; and &#8222;Delete Multiple&#8220; (preview) can offer a large boost for just a bit of setup work, enabling quick changes or migrations and accelerated the process tremendously.<\/p>\n\n\n\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button is-style-default\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/niklas-boesch.de\/wp-content\/uploads\/2024\/09\/CreateMultipleFlow.zip\">If you just want to tinker around with an example, download the flow and contacts sample here<\/a><\/div>\n<\/div>\n\n\n\n<div style=\"height:30px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Benchmarks<\/h2>\n\n\n\n<p>For comparison, I imported 10000 contacts with the fields &#8222;First Name&#8220;, &#8222;Last Name&#8220;, &#8222;Job Title&#8220;, &#8222;Email&#8220;, &#8222;Business Phone&#8220;, &#8222;Gender&#8220; and &#8222;Birthday&#8220;. The dataset used was a sample people dataset from <a href=\"https:\/\/www.datablist.com\/learn\/csv\/download-sample-csv-files\">Datablist<\/a>. I ran each import method three times to calculate an average time:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Excel CSV Import<\/strong>: 21:35 minutes<\/li>\n\n\n\n<li><strong>Dataflow Refresh<\/strong>: 10:16 minutes<\/li>\n\n\n\n<li><strong>Power Automate with &#8222;Add a new row&#8220; Loop<\/strong>: 18:45 minutes (loop execution took 17:04 minutes)<\/li>\n\n\n\n<li><strong>Power Automate with Create Multiple Bulk Message <\/strong>: 2:47 minutes (import time: 1:47, nearly 100 records per second!)<\/li>\n<\/ul>\n\n\n\n<p>Adding lookup and choice fields increased the import time by about 30% for all methods.<\/p>\n\n\n\n<p><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"400\" class=\"wp-image-448\" style=\"width: 800px;\" src=\"https:\/\/niklas-boesch.de\/wp-content\/uploads\/2024\/09\/chart.png\" alt=\"\" srcset=\"https:\/\/niklas-boesch.de\/wp-content\/uploads\/2024\/09\/chart.png 800w, https:\/\/niklas-boesch.de\/wp-content\/uploads\/2024\/09\/chart-300x150.png 300w, https:\/\/niklas-boesch.de\/wp-content\/uploads\/2024\/09\/chart-768x384.png 768w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><br><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Using Bulk Messages<\/h2>\n\n\n\n<p>When using Bulk Messages, there&#8217;s four primary steps you&#8217;ll need to execute, which can be wrapped in a Power App on demand trigger:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Retrieve your source data<\/strong><br>In our example, this is done using the &#8222;List rows present in a table&#8220; action with the Excel connector, using an xlsx file saved in OneDrive, but it can be any kind of action which results in you having your data in the flow.<br><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"537\" class=\"wp-image-438\" style=\"width: 800px;\" src=\"https:\/\/niklas-boesch.de\/wp-content\/uploads\/2024\/09\/List-Rows-from-Excel.png\" alt=\"\" srcset=\"https:\/\/niklas-boesch.de\/wp-content\/uploads\/2024\/09\/List-Rows-from-Excel.png 767w, https:\/\/niklas-boesch.de\/wp-content\/uploads\/2024\/09\/List-Rows-from-Excel-300x201.png 300w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><br><\/li>\n\n\n\n<li><strong>Format the data<\/strong><br>If the data is already available as an array and you only need to map to the right column names, this is a simple &#8222;Select&#8220; step. For each field you want to fill in your record, you&#8217;ll need to map the logical name of the field to your source data. However, there are some special cases:<br>\n<ul class=\"wp-block-list\">\n<li>Date Fields<br>To import dates, use the <code>formatDateTime<\/code> expression like this: <code>formatDateTime(item()?['Date&nbsp;of&nbsp;birth'],&nbsp;'yyyy-MM-dd')<\/code><\/li>\n\n\n\n<li>Choice Fields<br>You&#8217;ll need to set choice fields using the numerical Option Set Value (In our example I&#8217;m just setting gendercode to 1, which is the code for male). These values can be seen through the maker portal, or with the great <a href=\"https:\/\/chromewebstore.google.com\/detail\/level-up-for-dynamics-365\/bjnkkhimoaclnddigpphpgkfgeggokam\">Level up for Dynamics 365\/Power Apps<\/a> browser add in.<\/li>\n\n\n\n<li>Lookup Fields<br>To connect to other records, your formatted data needs to use OData Binding notation. For a detailed overview of OData Binding notation, check out <a href=\"https:\/\/syedsajidhussain.wordpress.com\/2020\/07\/13\/things-to-remember-when-using-dynamics-365-crm-api-aka-cds-or-odata-api\/\">Syed Sajid Hussain&#8217;s blog post<\/a>.<br>In our example, I&#8217;m using a fixed GUID, but it could also be a value from the source data or an alternate key.<\/li>\n\n\n\n<li>OData.type<br>For every record which is to be imported, you&#8217;ll need to add the &#8222;@odata.type&#8220; property, with a value of &#8222;Microsoft.Dynamics.CRM.[entitylogicalname]&#8220;. In our example, this is going to be &#8222;Microsoft.Dynamics.CRM.contact&#8220;. As you can see in the screenshot below, to add the property with the select step, we need to repeat the &#8222;@&#8220;, so Power Automate does not confuse the string with an attempt to create an expression.<br><br><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"581\" class=\"wp-image-440\" style=\"width: 800px;\" src=\"https:\/\/niklas-boesch.de\/wp-content\/uploads\/2024\/09\/Format-Source-Data-1.png\" alt=\"\" srcset=\"https:\/\/niklas-boesch.de\/wp-content\/uploads\/2024\/09\/Format-Source-Data-1.png 759w, https:\/\/niklas-boesch.de\/wp-content\/uploads\/2024\/09\/Format-Source-Data-1-300x218.png 300w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Chunk the Data<\/strong><br>To avoid <a href=\"https:\/\/learn.microsoft.com\/en-us\/power-apps\/developer\/data-platform\/bulk-operations?tabs=sdk#message-size-and-time-limits\">message size and time limits<\/a> large inputs need to be chunked and imported separately. <br>There&#8217;s a trade-off between large chunks leading to shorter import times, but increasing the chance that your import is going to fail because of those limits. Microsoft recommends 100-1000 records per message, depending on size of records and any plugins which may run during the import. <br><br>To find the sweet spot, I recommend setting the chunk length in a separate variable and experimenting with the most complicated subset of your import data. Don&#8217;t forget to have a margin of error, as the API and your environment does not have a fixed throughput.<br><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"311\" class=\"wp-image-439\" style=\"width: 800px;\" src=\"https:\/\/niklas-boesch.de\/wp-content\/uploads\/2024\/09\/Target-Chunk-Array.png\" alt=\"\" srcset=\"https:\/\/niklas-boesch.de\/wp-content\/uploads\/2024\/09\/Target-Chunk-Array.png 672w, https:\/\/niklas-boesch.de\/wp-content\/uploads\/2024\/09\/Target-Chunk-Array-300x117.png 300w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><\/li>\n\n\n\n<li><strong>Import Data<\/strong><br>After chunking, only the import is left. For that, an apply to each loop over our chunked data is used, containing only the &#8222;Invoke an HTTP request&#8220; with the preauthorized Entra ID connector.<br>The request can be set as seen below, please note that the entity set name needs to be a part of the url of the request.<br>The loop itself can be run in parallel as well to further speed up the process. I&#8217;ve used a concurrency level of 4 to keep to the <a href=\"https:\/\/learn.microsoft.com\/en-us\/power-apps\/developer\/data-platform\/api-limits?tabs=sdk#how-service-protection-api-limits-are-enforced\">Service protection limit<\/a> of 20 minutes execution time per 5 minute sliding window.<br><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"684\" class=\"wp-image-437\" style=\"width: 800px;\" src=\"https:\/\/niklas-boesch.de\/wp-content\/uploads\/2024\/09\/Import-Data.png\" alt=\"\" srcset=\"https:\/\/niklas-boesch.de\/wp-content\/uploads\/2024\/09\/Import-Data.png 739w, https:\/\/niklas-boesch.de\/wp-content\/uploads\/2024\/09\/Import-Data-300x257.png 300w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><\/li>\n<\/ul>\n\n\n\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/niklas-boesch.de\/wp-content\/uploads\/2024\/09\/CreateMultipleFlow.zip\">Download Solution and contact sample<\/a><\/div>\n<\/div>\n\n\n\n<div style=\"height:30px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Issues and Pitfalls<\/h2>\n\n\n\n<p>There are of course a few limits when using the Bulk Messages. If you suspect your use case may run into those, please look at the <a href=\"https:\/\/learn.microsoft.com\/en-us\/power-apps\/developer\/data-platform\/bulk-operations?tabs=webapi\">official documentation<\/a> for further details.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Any error during the call of a Bulk Message leads to the rollback of the entire changeset. <br>Only use Bulk Messages if you are sure your data is going to be formatted correctly and any lookup values you are setting are available in your target system.<\/li>\n\n\n\n<li>Not all tables are available for Bulk Messages<br>You can check if your use case is supported using a GET call described <a href=\"https:\/\/learn.microsoft.com\/en-us\/power-apps\/developer\/data-platform\/bulk-operations?tabs=webapi#availability-with-standard-tables\">here<\/a><\/li>\n\n\n\n<li>Message size and time limits<br>Any single request may not exceed 116.85 MB or an execution time limit of 2 minutes.<\/li>\n\n\n\n<li>API Limits<br>Using Bulk Messages instead of single action should lead to avoiding the &#8222;6000 requests per 5 minutes&#8220; limit, as each message is only a single request containing hundreds of records. However, according to documentation, each record included in the Target parameter of the message accrues to the <a href=\"https:\/\/learn.microsoft.com\/en-us\/power-platform\/admin\/api-request-limits-allocations\">Power Platform Request limit<\/a>.<br>Depending on your licenses, this may not be an issue for your use case but if you encounter problems, possible solutions include\n<ul class=\"wp-block-list\">\n<li>Changing the HTTP Request from a licensed user to a non-licensed user, which have request limits that are pooled at tenant level<\/li>\n\n\n\n<li>Purchase additional licenses, e.g. a Power Automate per flow pla<\/li>\n\n\n\n<li>Purchase a Power Platform Request capacity add-on<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Update and Upsert Multiple have different behaviours even when only updating records<br>I have not tried this myself, but according to documentation Update Multiple messages can not use alternate keys and ignore duplicate record in the payload, while Upsert Multiple can use alternate keys and fail when a duplicate is found in the payload.<\/li>\n\n\n\n<li>Error handling and monitoring<br>I have used Power Automate as an easy to follow example which anyone with access to an environment can play around with. However, for production use (especially in scheduled or unsupervised workflows), error handling and monitoring needs to be added and you should evaluate if Power Automate is the right tool for the job or if another tool like Azure Logic Apps with the more advanced monitoring and logging capabilities should be used instead (the core functionality will be the same, but the scaffolding may be more suited to business-critical processes).<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Final thoughts<\/h2>\n\n\n\n<p>Using Bulk Messages can save quite a bit of time when you need to import or update a lot of data at once or you expect multiple cycles of Import &#8211; Fix Errors &#8211; Re-Import. However, API limits still apply and need to be considered and accessing the functions directly via HTTP Message (and not through a paid tool) can lead to some fiddling regarding formatting and error handling. <\/p>\n\n\n\n<p>Have you tried using Bulk Messages to speed up your imports? Let me know how it went in the comments! If you ran into any issues\u2014especially with payload formatting\u2014I\u2019d love to hear about your experience.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Importing large datasets in Power Platform can be frustratingly slow, especially during supposedly one-time migrations for which you don&#8217;t want to license or use a paid integration platform. In one of my projects, constant changes to the data model turned a seemingly simple import of data from a legacy system into a days-long headache. Quite&hellip;&nbsp;<a href=\"https:\/\/niklas-boesch.de\/en\/2024\/09\/import-10000-records-in-under-2-minutes-using-createmultiple-in-power-automate\" rel=\"bookmark\">Read More &raquo;<span class=\"screen-reader-text\">Import 10,000 records in under 2 minutes &#8211; using CreateMultiple in Power Automate<\/span><\/a><\/p>","protected":false},"author":1,"featured_media":450,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"neve_meta_sidebar":"","neve_meta_container":"","neve_meta_enable_content_width":"","neve_meta_content_width":0,"neve_meta_title_alignment":"","neve_meta_author_avatar":"","neve_post_elements_order":"","neve_meta_disable_header":"","neve_meta_disable_footer":"","neve_meta_disable_title":"","_themeisle_gutenberg_block_has_review":false,"footnotes":""},"categories":[14,6],"tags":[9,10,15,11,12,8],"class_list":["post-434","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dataverse","category-power-automate","tag-bulk-message","tag-data-import","tag-dataverse","tag-excel","tag-migration","tag-power-automate"],"featured_image_src":"https:\/\/niklas-boesch.de\/wp-content\/uploads\/2024\/09\/lego-data-upload.png","author_info":{"display_name":"Niklas B\u00f6sch","author_link":"https:\/\/niklas-boesch.de\/en\/author\/niklasboesch"},"_links":{"self":[{"href":"https:\/\/niklas-boesch.de\/en\/wp-json\/wp\/v2\/posts\/434","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/niklas-boesch.de\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/niklas-boesch.de\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/niklas-boesch.de\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/niklas-boesch.de\/en\/wp-json\/wp\/v2\/comments?post=434"}],"version-history":[{"count":13,"href":"https:\/\/niklas-boesch.de\/en\/wp-json\/wp\/v2\/posts\/434\/revisions"}],"predecessor-version":[{"id":465,"href":"https:\/\/niklas-boesch.de\/en\/wp-json\/wp\/v2\/posts\/434\/revisions\/465"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/niklas-boesch.de\/en\/wp-json\/wp\/v2\/media\/450"}],"wp:attachment":[{"href":"https:\/\/niklas-boesch.de\/en\/wp-json\/wp\/v2\/media?parent=434"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/niklas-boesch.de\/en\/wp-json\/wp\/v2\/categories?post=434"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/niklas-boesch.de\/en\/wp-json\/wp\/v2\/tags?post=434"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}