{"id":79467,"date":"2020-04-27T07:42:23","date_gmt":"2020-04-27T05:42:23","guid":{"rendered":"https:\/\/prohoster.info\/blog\/administrirovanie\/operativnaya-analitika-v-mikroservisnoj-arhitekture-p%cc%b6o%cc%b6n%cc%b6ya%cc%b6t%cc%b6%cc%b6-%cc%b6i%cc%b6-%cc%b6p%cc%b6r%cc%b6o%cc%b6s%cc%b6t%cc%b6i%cc%b6t%cc%b6%cc%b6-pomoch-i-podskazat-postgres"},"modified":"2020-04-27T07:42:23","modified_gmt":"2020-04-27T05:42:23","slug":"operativnaya-analitika-v-mikroservisnoj-arhitekture-p%cc%b6o%cc%b6n%cc%b6ya%cc%b6t%cc%b6%cc%b6-%cc%b6i%cc%b6-%cc%b6p%cc%b6r%cc%b6o%cc%b6s%cc%b6t%cc%b6i%cc%b6t%cc%b6%cc%b6-pomoch-i-podskazat-postgres","status":"publish","type":"post","link":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/operativnaya-analitika-v-mikroservisnoj-arhitekture-p%cc%b6o%cc%b6n%cc%b6ya%cc%b6t%cc%b6%cc%b6-%cc%b6i%cc%b6-%cc%b6p%cc%b6r%cc%b6o%cc%b6s%cc%b6t%cc%b6i%cc%b6t%cc%b6%cc%b6-pomoch-i-podskazat-postgres","title":{"rendered":"Operational analytics in microservices architecture: understand and assist with Postgres FDW","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Microservice architecture, like everything in this world, has its pros and cons. Some processes become simpler with it, while others become more complex. In favor of speed and better scalability, sacrifices have to be made. One of these sacrifices is the complexity of analytics. In a monolith, all operational analytics can be reduced to SQL queries to an analytical replica, but in a multi-service architecture, each service has its own database, and it seems that one query will not suffice (or maybe it will?). For those interested in how we solved the problem of operational analytics in our company and how we learned to live with this solution \u2014 welcome.<\/p>\n<p><img decoding=\"async\" alt=\"Operational analytics in microservices architecture: understand and assist with Postgres FDW\" src=\"\/wp-content\/uploads\/2020\/04\/4296389f06d488999cc023dcaa3027f7.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nMy name is Pavel Sivash, and at DomClick, I work in the team responsible for maintaining the analytical data warehouse. Our activities can be loosely categorized as data engineering, but in reality, the range of tasks is much broader. There are standard data engineering tasks like ETL\/ELT, support and adaptation of tools for data analysis, and development of our own tools. In particular, for operational reporting, we decided to 'pretend' that we have a monolith and to provide analysts with a single database containing all the necessary data. <noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<p>In fact, we considered various options. We could have built a full-fledged storage system \u2014 we even tried, but to be honest, we never managed to align the frequent changes in logic with the rather slow process of building storage and making changes to it (if anyone succeeded, please share in the comments how). We could have told the analysts: \"Guys, learn Python and work with analytical replicas,\" but that would be an added requirement for staffing, and it seemed like something to avoid if possible. We decided to try using the FDW (Foreign Data Wrapper) technology: essentially, it\u2019s a standard dblink present in SQL standards, but with a much more user-friendly interface. Based on it, we developed a solution that ultimately became established, and that\u2019s where we settled. The details of it are a topic for a separate article, or maybe more than one, since there\u2019s so much to discuss: from schema synchronization to access management and anonymization of personal data. Also, it\u2019s important to clarify that this solution is not a replacement for real analytical databases and data warehouses; it only addresses a specific task.<\/p>\n<p>At a high level, it looks like this:<\/p>\n<p><img decoding=\"async\" alt=\"Operational analytics in microservices architecture: understand and assist with Postgres FDW\" src=\"\/wp-content\/uploads\/2020\/04\/574da29dfdb40706afe9e817e789a61b.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nThere is a PostgreSQL database where users can store their working data, and most importantly \u2014 analytical replicas of all services are connected to this database via FDW. This allows you to write queries across multiple databases, regardless of whether they are PostgreSQL, MySQL, MongoDB, or something else (a file, an API; if there isn\u2019t a suitable wrapper, you can write your own). Well, that\u2019s it, great! Shall we disperse?<\/p>\n<p>If everything ended that quickly and simply, there probably wouldn't be an article.<\/p>\n<p>It's important to clearly understand how PostgreSQL processes queries to remote servers. This seems logical, yet often goes unnoticed: PostgreSQL breaks a query into parts that run independently on remote servers, collects the data, and then performs the final computations itself, meaning the speed of executing a query will heavily depend on how it is written. It's also worth noting that when data comes from a remote server, it no longer has indexes or anything that assists the planner; therefore, we can only assist and inform it ourselves. And this is what I want to discuss in more detail.<\/p>\n<h1>Simple query and the plan with it<\/h1>\n<p>\nTo illustrate how PostgreSQL executes a query on a table with 6 million rows remotely, <a class=\"wpil_keyword_link\" href=\"https:\/\/prohoster.info\/en\/server\/dts-dronten\/\"   title=\"server\" data-wpil-keyword-link=\"linked\"  data-wpil-monitor-id=\"2589\">server<\/a>, let's look at a simple plan.<\/p>\n<pre><code class=\"sql\">explain analyze verbose  \nSELECT count(1)\nFROM fdw_schema.table;\n\nAggregate  (cost=418383.23..418383.24 rows=1 width=8) (actual time=3857.198..3857.198 rows=1 loops=1)\n  Output: count(1)\n  -&gt;  Foreign Scan on fdw_schema.\"table\"  (cost=100.00..402376.14 rows=6402838 width=0) (actual time=4.874..3256.511 rows=6406868 loops=1)\n        Output: \"table\".id, \"table\".is_active, \"table\".meta, \"table\".created_dt\n        Remote SQL: SELECT NULL FROM fdw_schema.table\nPlanning time: 0.986 ms\nExecution time: 3857.436 ms<\/code><\/pre>\n<p>\nUsing the VERBOSE instruction allows us to see the query that will be sent to the remote server and the results we will obtain for further processing (line RemoteSQL).<\/p>\n<p>Let's go a bit further and add some filters to our query: one by <b>boolean<\/b> field, one by occurrence <b>timestamp<\/b> in the interval, and one by <b>jsonb<\/b>.<\/p>\n<pre><code class=\"sql\">explain analyze verbose\nSELECT count(1)\nFROM fdw_schema.table \nWHERE is_active is True\nAND created_dt BETWEEN CURRENT_DATE - INTERVAL '7 month' \nAND CURRENT_DATE - INTERVAL '6 month'\nAND meta-&gt;&gt;'source' = 'test';\n\nAggregate  (cost=577487.69..577487.70 rows=1 width=8) (actual time=27473.818..25473.819 rows=1 loops=1)\n  Output: count(1)\n  -&gt;  Foreign Scan on fdw_schema.\"table\"  (cost=100.00..577469.21 rows=7390 width=0) (actual time=31.369..25372.466 rows=1360025 loops=1)\n        Output: \"table\".id, \"table\".is_active, \"table\".meta, \"table\".created_dt\n        Filter: ((\"table\".is_active IS TRUE) AND ((\"table\".meta -&gt;&gt; 'source'::text) = 'test'::text) AND (\"table\".created_dt &gt;= (('now'::cstring)::date - '7 mons'::interval)) AND (\"table\".created_dt &lt;= (((&#039;now&#039;::cstring)::date)::timestamp with time zone - &#039;6 mons&#039;::interval)))\n        Rows Removed by Filter: 5046843\n        Remote SQL: SELECT created_dt, is_active, meta FROM fdw_schema.table\nPlanning time: 0.665 ms\nExecution time: 27474.118 ms<\/code><\/pre>\n<p>\nThis is the moment to pay attention to when writing queries. The filters were not sent to the remote server, which means PostgreSQL pulls all 6 million rows to then filter them locally (line Filter) and perform aggregation. The key to success is to write the query so that the filters are passed to the remote machine, allowing us to retrieve and aggregate only the necessary rows. <\/p>\n<h1>That's some boolean nonsense<\/h1>\n<p>\nWith boolean fields, it's straightforward. In the original query, the problem arose due to the operator <b>is<\/b>. If we replace it with <b>=<\/b>, we will get the following result:<\/p>\n<pre><code class=\"sql\">explain analyze verbose\nSELECT count(1)\nFROM fdw_schema.table\nWHERE is_active = True\nAND created_dt BETWEEN CURRENT_DATE - INTERVAL '7 month' \nAND CURRENT_DATE - INTERVAL '6 month'\nAND meta-&gt;&gt;'source' = 'test';\n\nAggregate  (cost=508010.14..508010.15 rows=1 width=8) (actual time=19064.314..19064.314 rows=1 loops=1)\n  Output: count(1)\n  -&gt;  Foreign Scan on fdw_schema.\"table\"  (cost=100.00..507988.44 rows=8679 width=0) (actual time=33.035..18951.278 rows=1360025 loops=1)\n        Output: \"table\".id, \"table\".is_active, \"table\".meta, \"table\".created_dt\n        Filter: (((\"table\".meta -&gt;&gt; 'source'::text) = 'test'::text) AND (\"table\".created_dt &gt;= (('now'::cstring)::date - '7 mons'::interval)) AND (\"table\".created_dt &lt;= (((&#039;now&#039;::cstring)::date)::timestamp with time zone - &#039;6 mons&#039;::interval)))\n        Rows Removed by Filter: 3567989\n        Remote SQL: SELECT created_dt, meta FROM fdw_schema.table WHERE (is_active)\nPlanning time: 0.834 ms\nExecution time: 19064.534 ms<\/code><\/pre>\n<p>\nAs you can see, the filter was sent to the remote server, and the execution time decreased from 27 to 19 seconds. <\/p>\n<p>It's worth noting that the operator <b>is<\/b> differs from the operator <b>=<\/b> in that it can handle Null values. This means that <b>is not True<\/b> in the filter will leave False and Null values, while <b>!= True<\/b> will leave only False values. Therefore, when replacing the operator <b>is not<\/b> it is necessary to provide two conditions in the filter using the OR operator, for example, <b>WHERE (col != True) OR (col is null)<\/b>.<\/p>\n<p>We've sorted out boolean values, now let's move on. Meanwhile, let's revert the filter for boolean values to its original state to independently examine the effect of other changes.<\/p>\n<h1>timestamptz? hz<\/h1>\n<p>\nIn fact, I often have to experiment with how to correctly write a query involving remote servers, and only then look for an explanation of why it behaves this way. There is very little information available online regarding this matter. During experiments, we found that a filter by fixed date works well on the remote server, but when we want to set the date dynamically, such as with now() or CURRENT_DATE, this does not happen. In our example, we added such a filter to ensure that the created_at column contained data from exactly 1 month ago (BETWEEN CURRENT_DATE - INTERVAL '7 month' AND CURRENT_DATE - INTERVAL '6 month'). What did we do in this case?<\/p>\n<pre><code class=\"sql\">explain analyze verbose\nSELECT count(1)\nFROM fdw_schema.table \nWHERE is_active is True\nAND created_dt &gt;= (SELECT CURRENT_DATE::timestamptz - INTERVAL '7 month') \nAND created_dt &gt;'source' = 'test';\n\nAggregate  (cost=306875.17..306875.18 rows=1 width=8) (actual time=4789.114..4789.115 rows=1 loops=1)\n  Output: count(1)\n  InitPlan 1 (returns $0)\n    -&gt;  Result  (cost=0.00..0.02 rows=1 width=8) (actual time=0.007..0.008 rows=1 loops=1)\n          Output: ((('now'::cstring)::date)::timestamp with time zone - '7 mons'::interval)\n  InitPlan 2 (returns $1)\n    -&gt;  Result  (cost=0.00..0.02 rows=1 width=8) (actual time=0.002..0.002 rows=1 loops=1)\n          Output: ((('now'::cstring)::date)::timestamp with time zone - '6 mons'::interval)\n  -&gt;  Foreign Scan on fdw_schema.\"table\"  (cost=100.02..306874.86 rows=105 width=0) (actual time=23.475..4681.419 rows=1360025 loops=1)\n        Output: \"table\".id, \"table\".is_active, \"table\".meta, \"table\".created_dt\n        Filter: ((\"table\".is_active IS TRUE) AND ((\"table\".meta -&gt;&gt; 'source'::text) = 'test'::text))\n        Rows Removed by Filter: 76934\n        Remote SQL: SELECT is_active, meta FROM fdw_schema.table WHERE ((created_dt &gt;= $1::timestamp with time zone)) AND ((created_dt &lt; $2::timestamp with time zone))\nPlanning time: 0.703 ms\nExecution time: 4789.379 ms<\/code><\/pre>\n<p>\nWe suggested the planner to pre-compute the date in the subquery and pass the ready variable into the filter. This hint gave us a fantastic result \u2014 the query became almost six times faster!<\/p>\n<p>Once again, it's crucial to be careful here: the data type in the subquery must match the type of the field we are filtering by. Otherwise, the planner will decide that the types are different and will need to fetch all the data first, then filter it locally.<\/p>\n<p>Let\u2019s revert the date filter to its original value.<\/p>\n<h1>Freddy vs. Jsonb<\/h1>\n<p>\nIn general, boolean fields and dates have already significantly sped up our query. However, there was still one more data type left. Honestly, the battle for filtering it is still not over, though we have had successes here too. So here\u2019s how we managed to pass the filter for <b>jsonb<\/b> the field to the remote server.<\/p>\n<pre><code class=\"sql\">explain analyze verbose\nSELECT count(1)\nFROM fdw_schema.table \nWHERE is_active is True\nAND created_dt BETWEEN CURRENT_DATE - INTERVAL '7 month' \nAND CURRENT_DATE - INTERVAL '6 month'\nAND meta @&gt; '{\"source\":\"test\"}'::jsonb;\n\nAggregate  (cost=245463.60..245463.61 rows=1 width=8) (actual time=6727.589..6727.590 rows=1 loops=1)\n  Output: count(1)\n  -&gt;  Foreign Scan on fdw_schema.\"table\"  (cost=1100.00..245459.90 rows=1478 width=0) (actual time=16.213..6634.794 rows=1360025 loops=1)\n        Output: \"table\".id, \"table\".is_active, \"table\".meta, \"table\".created_dt\n        Filter: ((\"table\".is_active IS TRUE) AND (\"table\".created_dt &gt;= (('now'::cstring)::date - '7 mons'::interval)) AND (\"table\".created_dt  '{\"source\": \"test\"}'::jsonb))\nPlanning time: 0.747 ms\nExecution time: 6727.815 ms<\/code><\/pre>\n<p>\nInstead of filtering operators, it's necessary to use the existence operator. <b>jsonb<\/b> in another. 7 seconds instead of the original 29. So far, this is the only successful option for transferring filters across <b>jsonb<\/b> to the remote server, but one limitation must be considered: we are using version 9.6 of the database, however, by the end of April we plan to complete the final tests and move to version 12. Once we upgrade, we will report on how it affected performance, as there are quite a few changes we are hopeful about: json_path, new CTE behavior, push down (existing since version 10). We are eager to try this out soon.<\/p>\n<h1>Finish him<\/h1>\n<p>\nWe checked how each change affects query speed individually. Now, let's see what happens when all three filters are correctly applied.<\/p>\n<pre><code class=\"sql\">explain analyze verbose\nSELECT count(1)\nFROM fdw_schema.table \nWHERE is_active = True\nAND created_dt &gt;= (SELECT CURRENT_DATE::timestamptz - INTERVAL '7 month') \nAND created_dt  '{\"source\":\"test\"}'::jsonb;\n\nAggregate  (cost=322041.51..322041.52 rows=1 width=8) (actual time=2278.867..2278.867 rows=1 loops=1)\n  Output: count(1)\n  InitPlan 1 (returns $0)\n    -&gt;  Result  (cost=0.00..0.02 rows=1 width=8) (actual time=0.010..0.010 rows=1 loops=1)\n          Output: ((('now'::cstring)::date)::timestamp with time zone - '7 mons'::interval)\n  InitPlan 2 (returns $1)\n    -&gt;  Result  (cost=0.00..0.02 rows=1 width=8) (actual time=0.003..0.003 rows=1 loops=1)\n          Output: ((('now'::cstring)::date)::timestamp with time zone - '6 mons'::interval)\n  -&gt;  Foreign Scan on fdw_schema.\"table\"  (cost=100.02..322041.41 rows=25 width=0) (actual time=8.597..2153.809 rows=1360025 loops=1)\n        Output: \"table\".id, \"table\".is_active, \"table\".meta, \"table\".created_dt\n        Remote SQL: SELECT NULL FROM fdw_schema.table WHERE (is_active) AND ((created_dt &gt;= $1::timestamp with time zone)) AND ((created_dt  '{\"source\": \"test\"}'::jsonb))\nPlanning time: 0.820 ms\nExecution time: 2279.087 ms<\/code><\/pre>\n<p>\nYes, the query looks more complex, it's a necessary trade-off, but the execution speed is 2 seconds, which is more than 10 times faster! And we're talking about a simple query on a relatively small dataset. For real queries, we achieved performance gains of up to several hundred times.<\/p>\n<p>In conclusion: if you are using PostgreSQL with FDW, always check that all filters are passed to the remote server, and happiness will be yours... At least until you reach joins between tables from different <a class=\"wpil_keyword_link\" href=\"https:\/\/prohoster.info\/en\/server\/\"   title=\"servers\" data-wpil-keyword-link=\"linked\"  data-wpil-monitor-id=\"1482\">servers<\/a>. But that's already a story for another article.<\/p>\n<p>Thank you for your attention! I would love to hear your questions, comments, as well as stories about your experiences in the comments.<br \/>\n<br \/>Source: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/domclick\/blog\/498018\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u041c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430, \u043a\u0430\u043a \u0438 \u0432\u0441\u0435 \u0432 \u044d\u0442\u043e\u043c \u043c\u0438\u0440\u0435, \u0438\u043c\u0435\u0435\u0442 \u0441\u0432\u043e\u0438 \u043f\u043b\u044e\u0441\u044b \u0438 \u0441\u0432\u043e\u0438 \u043c\u0438\u043d\u0443\u0441\u044b. \u041e\u0434\u043d\u0438 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b \u0441 \u043d\u0435\u0439 \u0441\u0442\u0430\u043d\u043e\u0432\u044f\u0442\u0441\u044f \u043f\u0440\u043e\u0449\u0435, \u0434\u0440\u0443\u0433\u0438\u0435 \u2014 \u0441\u043b\u043e\u0436\u043d\u0435\u0435. \u0418 \u0432 \u0443\u0433\u043e\u0434\u0443 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u0438 \u043b\u0443\u0447\u0448\u0435\u0439 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u0443\u0435\u043c\u043e\u0441\u0442\u0438 \u043d\u0443\u0436\u043d\u043e \u043f\u0440\u0438\u043d\u043e\u0441\u0438\u0442\u044c \u0441\u0432\u043e\u0438 \u0436\u0435\u0440\u0442\u0432\u044b. \u041e\u0434\u043d\u0430 \u0438\u0437 \u043d\u0438\u0445 \u2014 \u0443\u0441\u043b\u043e\u0436\u043d\u0435\u043d\u0438\u0435 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0438. \u0415\u0441\u043b\u0438 \u0432 \u043c\u043e\u043d\u043e\u043b\u0438\u0442\u0435 \u0432\u0441\u044e \u043e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u0443\u044e \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0443 \u043c\u043e\u0436\u043d\u043e \u0441\u0432\u0435\u0441\u0442\u0438 \u043a SQL \u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c \u043a \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0440\u0435\u043f\u043b\u0438\u043a\u0435, [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":79468,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-79467","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u041c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430, \u043a\u0430\u043a \u0438 \u0432\u0441\u0435 \u0432 \u044d\u0442\u043e\u043c \u043c\u0438\u0440\u0435, \u0438\u043c\u0435\u0435\u0442 \u0441\u0432\u043e\u0438 \u043f\u043b\u044e\u0441\u044b \u0438 \u0441\u0432\u043e\u0438 \u043c\u0438\u043d\u0443\u0441\u044b. \u041e\u0434\u043d\u0438 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b \u0441 \u043d\u0435\u0439 \u0441\u0442\u0430\u043d\u043e\u0432\u044f\u0442\u0441\u044f \u043f\u0440\u043e\u0449\u0435, \u0434\u0440\u0443\u0433\u0438\u0435 \u2014 \u0441\u043b\u043e\u0436\u043d\u0435\u0435.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/operativnaya-analitika-v-mikroservisnoj-arhitekture-p%cc%b6o%cc%b6n%cc%b6ya%cc%b6t%cc%b6%cc%b6-%cc%b6i%cc%b6-%cc%b6p%cc%b6r%cc%b6o%cc%b6s%cc%b6t%cc%b6i%cc%b6t%cc%b6%cc%b6-pomoch-i-podskazat-postgres\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u041e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u0430\u044f \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0430 \u0432 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0435: \u043f\u0336\u043e\u0336\u043d\u0336\u044f\u0336\u0442\u0336\u044c\u0336 \u0336\u0438\u0336 \u0336\u043f\u0336\u0440\u0336\u043e\u0336\u0441\u0336\u0442\u0336\u0438\u0336\u0442\u0336\u044c\u0336 \u043f\u043e\u043c\u043e\u0447\u044c \u0438 \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u0430\u0442\u044c Postgres FDW | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u041c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430, \u043a\u0430\u043a \u0438 \u0432\u0441\u0435 \u0432 \u044d\u0442\u043e\u043c \u043c\u0438\u0440\u0435, \u0438\u043c\u0435\u0435\u0442 \u0441\u0432\u043e\u0438 \u043f\u043b\u044e\u0441\u044b \u0438 \u0441\u0432\u043e\u0438 \u043c\u0438\u043d\u0443\u0441\u044b. \u041e\u0434\u043d\u0438 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b \u0441 \u043d\u0435\u0439 \u0441\u0442\u0430\u043d\u043e\u0432\u044f\u0442\u0441\u044f \u043f\u0440\u043e\u0449\u0435, \u0434\u0440\u0443\u0433\u0438\u0435 \u2014 \u0441\u043b\u043e\u0436\u043d\u0435\u0435.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/operativnaya-analitika-v-mikroservisnoj-arhitekture-p%cc%b6o%cc%b6n%cc%b6ya%cc%b6t%cc%b6%cc%b6-%cc%b6i%cc%b6-%cc%b6p%cc%b6r%cc%b6o%cc%b6s%cc%b6t%cc%b6i%cc%b6t%cc%b6%cc%b6-pomoch-i-podskazat-postgres\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-04-27T05:42:23+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-04-27T05:42:23+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Operational Analytics in Microservices Architecture: Understand and Assist with Postgres FDW | ProHoster","description":"Microservice architecture, like everything in this world, has its pros and cons. Some processes become simpler with it, while others become more complex.","canonical_url":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/operativnaya-analitika-v-mikroservisnoj-arhitekture-p%cc%b6o%cc%b6n%cc%b6ya%cc%b6t%cc%b6%cc%b6-%cc%b6i%cc%b6-%cc%b6p%cc%b6r%cc%b6o%cc%b6s%cc%b6t%cc%b6i%cc%b6t%cc%b6%cc%b6-pomoch-i-podskazat-postgres","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"en_US","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u041e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u0430\u044f \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0430 \u0432 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0435: \u043f\u0336\u043e\u0336\u043d\u0336\u044f\u0336\u0442\u0336\u044c\u0336 \u0336\u0438\u0336 \u0336\u043f\u0336\u0440\u0336\u043e\u0336\u0441\u0336\u0442\u0336\u0438\u0336\u0442\u0336\u044c\u0336 \u043f\u043e\u043c\u043e\u0447\u044c \u0438 \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u0430\u0442\u044c Postgres FDW | ProHoster","og:description":"\u041c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430, \u043a\u0430\u043a \u0438 \u0432\u0441\u0435 \u0432 \u044d\u0442\u043e\u043c \u043c\u0438\u0440\u0435, \u0438\u043c\u0435\u0435\u0442 \u0441\u0432\u043e\u0438 \u043f\u043b\u044e\u0441\u044b \u0438 \u0441\u0432\u043e\u0438 \u043c\u0438\u043d\u0443\u0441\u044b. \u041e\u0434\u043d\u0438 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b \u0441 \u043d\u0435\u0439 \u0441\u0442\u0430\u043d\u043e\u0432\u044f\u0442\u0441\u044f \u043f\u0440\u043e\u0449\u0435, \u0434\u0440\u0443\u0433\u0438\u0435 \u2014 \u0441\u043b\u043e\u0436\u043d\u0435\u0435.","og:url":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/operativnaya-analitika-v-mikroservisnoj-arhitekture-p%cc%b6o%cc%b6n%cc%b6ya%cc%b6t%cc%b6%cc%b6-%cc%b6i%cc%b6-%cc%b6p%cc%b6r%cc%b6o%cc%b6s%cc%b6t%cc%b6i%cc%b6t%cc%b6%cc%b6-pomoch-i-podskazat-postgres","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-04-27T05:42:23+00:00","article:modified_time":"2020-04-27T05:42:23+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"79467","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 16:37:24","updated":"2026-02-09 21:38:01","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts\/79467","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/comments?post=79467"}],"version-history":[{"count":2,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts\/79467\/revisions"}],"predecessor-version":[{"id":159871,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts\/79467\/revisions\/159871"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/media\/79468"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/media?parent=79467"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/categories?post=79467"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/tags?post=79467"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}