{"id":74953,"date":"2020-03-22T08:42:22","date_gmt":"2020-03-22T05:42:22","guid":{"rendered":"https:\/\/prohoster.info\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy"},"modified":"2020-03-22T08:42:22","modified_gmt":"2020-03-22T05:42:22","slug":"dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","status":"publish","type":"post","link":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","title":{"rendered":"DBA: we efficiently organize synchronizations and imports","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>When dealing with complex processing of large datasets (various <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/ETL\">ETL processes<\/a><\/noindex>: imports, conversions, and synchronization with an external source), the need often arises <b>to temporarily 'remember' and quickly process<\/b> something substantial.<\/p>\n<p>The typical task of this kind usually sounds something like this: <i>\"Here, <noindex><a rel=\"nofollow\" href=\"https:\/\/sbis.ru\/accounting\">the accounting department exported from the client bank<\/a><\/noindex> the latest incoming payments, we need to quickly upload them to the site and link them to invoices\"<\/i><\/p>\n<p>However, when the volume of this 'something' starts to be measured in hundreds of megabytes, and the service must continue to operate with the database in 24\/7 mode, many side effects arise that will complicate your life.<br \/>\n<img decoding=\"async\" alt=\"DBA: we efficiently organize synchronizations and imports\" src=\"\/wp-content\/uploads\/2020\/03\/f74afb2cd6f5f8de26a0932166933c95.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nTo cope with them in PostgreSQL (and not just there), certain optimization features can be used that will allow for faster processing with less resource consumption.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>1. Where to load?<\/h2>\n<p>\nFirst, let's determine where we can load the data we want to 'process'.<\/p>\n<h3>1.1. Temporary tables (TEMPORARY TABLE)<\/h3>\n<p>\nIn principle, for PostgreSQL, temporary tables are just like any other tables. Therefore, superstitions like <i><b>\"everything is stored only in memory, and it can run out\"<\/b><\/i>are incorrect. However, there are several significant differences.<\/p>\n<h4>A separate 'namespace' for each connection to the database<\/h4>\n<p>\nIf two connections try to simultaneously execute <code>CREATE TABLE x<\/code>, then someone will definitely get <b>a uniqueness violation error<\/b> of database objects.<\/p>\n<p>However, if both try to execute <code>CREATE <b>TEMPORARY<\/b> TABLE x<\/code>, then both will do it successfully, and each will receive <b>its own instance<\/b> of the table. There will be nothing in common between them.<\/p>\n<h4>'Self-destruction' upon disconnection<\/h4>\n<p>\nWhen a connection is closed, all temporary tables are automatically deleted, so there's no point in manually executing <code>DROP TABLE x<\/code> unless\u2026<\/p>\n<p>If you are working through <b>pgbouncer in transaction mode<\/b>, then the database still considers that the connection is still active, and that temporary table still exists in it.<\/p>\n<p>Therefore, trying to create it again from another connection to pgbouncer will lead to an error. But this can be bypassed by using <code>CREATE TEMPORARY TABLE <b>IF NOT EXISTS<\/b> x<\/code>.<\/p>\n<p>In fact, it's better not to do that, as you might unexpectedly discover leftover data from the previous owner. Instead, it's much better to read the manual and see that when creating a table, you have the option to specify <code>ON COMMIT <b>DROP<\/b><\/code> \u2014 meaning that upon completing the transaction, the table will be automatically deleted.<\/p>\n<h4>Non-replication<\/h4>\n<p>\nDue to being tied to a specific connection, temporary tables are not replicated. However, <b>this eliminates the need for double data writing<\/b> in heap + WAL, so INSERT\/UPDATE\/DELETE operations on it are significantly faster.<\/p>\n<p>But since a temporary table is still 'almost ordinary,' it cannot be created on a replica either. At least, not for now, although the corresponding patch has been around for a long time.<\/p>\n<h3>1.2. Unlogged Tables (UNLOGGED TABLE)<\/h3>\n<p>\nBut what if, for instance, you have some bulky ETL process that cannot be realized within a single transaction, and you do have <b>pgbouncer in transaction mode<\/b>?..<\/p>\n<p>Or if the data flow is so large that <b>the bandwidth of a single connection<\/b> to the DB (i.e., one process on the CPU) is insufficient?..<\/p>\n<p>Or if part of the operations are going <b>asynchronously<\/b> in different connections?..<\/p>\n<p>Here, there\u2019s only one option \u2014 <b>temporarily creating a non-temporary table<\/b>. A pun, right? Meaning:<\/p>\n<ul>\n<li>I created 'my' tables with maximally random names to avoid any collisions<\/li>\n<li><b>Extract<\/b>: I loaded the data into them from an external source<\/li>\n<li><b>Transform<\/b>: I transformed and filled the key linking fields<\/li>\n<li><b>Load<\/b>: I poured the prepared data into target tables<\/li>\n<li>and deleted 'my' tables.<\/li>\n<\/ul>\n<p>\nAnd now \u2014 the fly in the ointment. Essentially, <b>all writing in PostgreSQL occurs twice<\/b> \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/postgrespro\/blog\/461523\/\">first in WAL<\/a><\/noindex>, and then into the bodies of tables\/indices. All of this is done to support ACID and maintain data visibility between <code>COMMIT<\/code>'nested and <code>ROLLBACK<\/code>'nested transactions.<\/p>\n<p>But we don't need that! Either our entire process <b>has completely succeeded or it hasn't.<\/b>It doesn\u2019t matter how many intermediate transactions there are \u2014 we\u2019re not interested in 'continuing the process from the middle,' especially when it\u2019s unclear where it was.<\/p>\n<p>To address this, PostgreSQL developers introduced the concept of <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-createtable#SQL-CREATETABLE-UNLOGGED\">unlogged (UNLOGGED) tables in version 9.1.<\/a><\/noindex>:<\/p>\n<blockquote><p>With this specification, the table is created as unlogged. Data written into unlogged tables does not go through the write-ahead log (see Chapter 29), resulting in such tables <b>work much faster than usual<\/b>. However, they are not protected against failure; in case of failure or sudden server shutdown, the unlogged table <b>is automatically truncated<\/b>. Furthermore, the content of the unlogged table <b>is not replicated<\/b> to standby servers. Any indexes created for the unlogged table automatically become unlogged.<\/p><\/blockquote>\n<p>In short, <b>it will be significantly faster<\/b>, but if the DB server crashes \u2014 it will be unpleasant. But how often does this happen, and does your ETL process handle it correctly \"midway\" after the DB is \"revived\"?<\/p>\n<p>If not, and the case above resembles yours \u2014 use <code>UNLOGGED<\/code>, but never <b>enable this attribute on real tables<\/b>, the data from which you value.<\/p>\n<h3>1.3. ON COMMIT { DELETE ROWS | DROP }<\/h3>\n<p>\nThis construct allows you to specify automatic behavior upon transaction completion when creating a table.<\/p>\n<p>About <code>ON COMMIT <b>DROP<\/b><\/code> I already wrote above, it generates <code>DROP TABLE<\/code>, but the situation with <code>ON COMMIT <b>DELETE ROWS<\/b><\/code> is more interesting \u2014 it generates <code>TRUNCATE TABLE<\/code>.<\/p>\n<p>Since the entire infrastructure for storing the metadata of the temporary table is exactly the same as that of a regular one, <b>the constant creation-deletion of temporary tables leads to significant \"bloating\" of system tables<\/b> pg_class, pg_attribute, pg_attrdef, pg_depend,\u2026<\/p>\n<p>Now imagine you have a worker on a direct connection with the DB, who opens a new transaction every second, creates, fills, processes, and deletes a temporary table\u2026 Garbage will accumulate excessively in the system tables, resulting in extra slowdowns with each operation.<\/p>\n<p>In general, don\u2019t do that! In this case, it is much more efficient <code>to take the creation of the temporary table out of the transaction cycle \u2014 then at the beginning of each new transaction, the table will already<\/code> exist <b>(saving the call<\/b> ), but <code>CREATE<\/code>it will be empty <b>, thanks to<\/b>TRUNCATE <code>(we also saved that call) upon completion of the previous transaction.<\/code> 1.4. LIKE\u2026 INCLUDING \u2026<\/p>\n<h3>1.4. LIKE\u2026 INCLUDING \u2026<\/h3>\n<p>\nBut laziness is the engine of progress! Therefore,<\/p>\n<p>it is much simpler to create a new table \"by example\": <b>CREATE TEMPORARY TABLE import_table(\n  LIKE target_table\n);<\/b> creating a new table 'by example'<\/p>\n<pre><code class=\"sql\">can be much simpler: CREATE TEMPORARY TABLE import_table(\n  LIKE target_table\n);<\/code><\/pre>\n<p>\nSince a lot of data can be generated in this table later, searching through it won't be fast at all. But there\u2019s a traditional solution to this \u2014 indexes! And yes, <b>temporary tables can also have indexes.<\/b>.<\/p>\n<p>Since, often, the needed indexes coincide with those of the target table, you can simply write <code>LIKE target_table <b>INCLUDING INDEXES<\/b><\/code>.<\/p>\n<p>If you also need <code>DEFAULT<\/code>-values (for example, to fill in primary key values), you can use <code>LIKE target_table <b>INCLUDING DEFAULTS<\/b><\/code>. Or just \u2014 <code>LIKE target_table <b>INCLUDING ALL<\/b><\/code> \u2014 it will copy defaults, indexes, constraints,\u2026<\/p>\n<p>But here you need to understand that if you created the <b>import table with indexes right away, then the data will be loaded slower<\/b>, than if you load everything first and then apply the indexes \u2014 take a look at how <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/app-pgdump\">pg_dump<\/a><\/noindex>.<\/p>\n<p>In general, <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-createtable\">RTFM<\/a><\/noindex>!<\/p>\n<h2>2. How to write?<\/h2>\n<p>\nI'll say simply \u2014 use <code><noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-copy\">COPY<\/a><\/noindex><\/code>-stream instead of \"batch\" <code>INSERT<\/code>, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.citusdata.com\/blog\/2017\/11\/08\/faster-bulk-loading-in-postgresql-with-copy\/\">speeds things up drastically.<\/a><\/noindex>. You can even do it directly from a pre-formed file.<\/p>\n<h2>3. How to process?<\/h2>\n<p>\nSo, let's say our input looks something like this:<\/p>\n<ul>\n<li>you have a table in the database with client data containing <b>1M records<\/b><\/li>\n<li>every day the client sends you a new <b>full \"image\"<\/b><\/li>\n<li>from experience, you know that from time to time <b>there are no more than 10K record changes<\/b><\/li>\n<\/ul>\n<p>\nA classic example of such a situation is <noindex><a rel=\"nofollow\" href=\"https:\/\/www.gnivc.ru\/technical_support\/classifiers_reference\/kladr\/\">the KLADR database<\/a><\/noindex> \u2014 there are many addresses in total, but there are only a few changes (renaming of localities, merging of streets, the appearance of new buildings) in each weekly export even on a national scale.<\/p>\n<h3>3.1. Full synchronization algorithm<\/h3>\n<p>\nFor simplicity, let's assume that you don\u2019t even need to restructure the data \u2014 just format the table appropriately, that is:<\/p>\n<ul>\n<li><b>remove<\/b> everything that no longer exists<\/li>\n<li><b>update<\/b> everything that was already there and needs updating<\/li>\n<li><b>insert<\/b> everything that was not there yet<\/li>\n<\/ul>\n<p>\nWhy should operations be done in this specific order? Because this is how the table size will minimally increase (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/491366\/\">remember about MVCC!<\/a><\/noindex>).<\/p>\n<h4>DELETE FROM dst<\/h4>\n<p>\nNo, of course, you can manage with just two operations:<\/p>\n<ul>\n<li><b>remove<\/b> (<code>DELETE<\/code>) everything at all<\/li>\n<li><b>insert<\/b> everything from the new image<\/li>\n<\/ul>\n<p>\nBut in doing so, due to MVCC, <b>the table size will increase by exactly double<\/b>! Getting +1M record images in the table due to updating 10K \u2014 that\u2019s quite the redundancy\u2026<\/p>\n<h4>TRUNCATE dst<\/h4>\n<p>\nA more experienced developer knows that the entire table can be cleaned up quite cheaply:<\/p>\n<ul>\n<li><b>clean up<\/b> (<code>(we also saved that call) upon completion of the previous transaction.<\/code>) the entire table<\/li>\n<li><b>insert<\/b> everything from the new image<\/li>\n<\/ul>\n<p>\nAn effective method, <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/481866\/\">sometimes quite applicable.<\/a><\/noindex>, but there is a challenge\u2026 Inserting 1M records will take a long time, so we cannot afford to leave the table empty all this time (as would happen without wrapping in a single transaction).<\/p>\n<p>This means that:<\/p>\n<ul>\n<li>we are starting <b>a long transaction<\/b><\/li>\n<li><code>(we also saved that call) upon completion of the previous transaction.<\/code> imposes <b>AccessExclusive<\/b>-locking<\/li>\n<li>we spend a lot of time on insertion while everyone else <b>cannot even <code>SELECT<\/code><\/b><\/li>\n<\/ul>\n<p>\nSomething is going wrong...<\/p>\n<h4>ALTER TABLE\u2026 RENAME\u2026 \/ DROP TABLE \u2026<\/h4>\n<p>\nAs an option \u2014 load everything into a separate new table, and then simply rename it in place of the old one. A couple of annoying details:<\/p>\n<ul>\n<li>it also <b>AccessExclusive<\/b>, though significantly less time-consuming<\/li>\n<li>all query plans\/statistics for this table are reset, <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/479656\/\">you need to run ANALYZE<\/a><\/noindex><\/li>\n<li><b>all foreign keys<\/b> (FK) to the table break<\/li>\n<\/ul>\n<p>\nThere was a WIP patch from Simon Riggs that proposed to make <code>the ALTER<\/code>-operation to replace the table's body at the file level, without touching the statistics and FK, but it did not gather a quorum.<\/p>\n<h4>DELETE, UPDATE, INSERT<\/h4>\n<p>\nSo, we settle on the non-blocking option of three operations. Almost three... How can we do this most efficiently?<\/p>\n<pre><code class=\"sql\">-- we do everything within a transaction so that no one sees the 'intermediate' states\nBEGIN;\n\n-- create a temporary table with the imported data\nCREATE TEMPORARY TABLE tmp(\n  LIKE dst INCLUDING INDEXES -- like-for-like, including indexes\n) ON COMMIT DROP; -- we don't need it outside the transaction\n\n-- quickly insert the new image via COPY\nCOPY tmp FROM STDIN;\n-- ...\n-- .\n\n-- delete missing entries\nDELETE FROM\n  dst D\nUSING\n  dst X\nLEFT JOIN\n  tmp Y\n    USING(pk1, pk2) -- primary key fields\nWHERE\n  (D.pk1, D.pk2) = (X.pk1, X.pk2) AND\n  Y IS NOT DISTINCT FROM NULL; -- 'anti-join'\n\n-- update remaining entries\nUPDATE\n  dst D\nSET\n  (f1, f2, f3) = (T.f1, T.f2, T.f3)\nFROM\n  tmp T\nWHERE\n  (D.pk1, D.pk2) = (T.pk1, T.pk2) AND\n  (D.f1, D.f2, D.f3) IS DISTINCT FROM (T.f1, T.f2, T.f3); -- no need to update matching ones\n\n-- insert missing entries\nINSERT INTO\n  dst\nSELECT\n  T.*\nFROM\n  tmp T\nLEFT JOIN\n  dst D\n    USING(pk1, pk2)\nWHERE\n  D IS NOT DISTINCT FROM NULL;\n\nCOMMIT;\n<\/code><\/pre>\n<p><\/p>\n<h3>3.2. Post-processing import<\/h3>\n<p>\nIn the same CLADR, all changed records need to undergo additional post-processing \u2014 normalization, keyword extraction, bringing into the right structures. But how to know \u2014 <b>what exactly was changed<\/b>, without complicating the synchronization code, ideally, without touching it at all?<\/p>\n<p>If write access during synchronization is only granted to your process, you can use a trigger to collect all changes for us:<\/p>\n<pre><code class=\"sql\">-- Target tables\nCREATE TABLE kladr(...);\nCREATE TABLE kladr_house(...);\n\n-- Change history tables\nCREATE TABLE kladr$log(\n  ro kladr, -- here lie the complete copies of old\/new records\n  rn kladr\n);\n\nCREATE TABLE kladr_house$log(\n  ro kladr_house,\n  rn kladr_house\n);\n\n-- General function for logging changes\nCREATE OR REPLACE FUNCTION diff$log() RETURNS trigger AS $$\nDECLARE\n  dst varchar = TG_TABLE_NAME || '$log';\n  stmt text = '';\nBEGIN\n  -- check if logging is necessary when updating a record\n  IF TG_OP = 'UPDATE' THEN\n    IF NEW IS NOT DISTINCT FROM OLD THEN\n      RETURN NEW;\n    END IF;\n  END IF;\n  -- create a log record\n  stmt = 'INSERT INTO ' || dst::text || '(ro,rn)VALUES(';\n  CASE TG_OP\n    WHEN 'INSERT' THEN\n      EXECUTE stmt || 'NULL,$1)' USING NEW;\n    WHEN 'UPDATE' THEN\n      EXECUTE stmt || '$1,$2)' USING OLD, NEW;\n    WHEN 'DELETE' THEN\n      EXECUTE stmt || '$1,NULL)' USING OLD;\n  END CASE;\n  RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n<\/code><\/pre>\n<p>\nNow we can apply (or enable through before starting synchronization) the triggers <code>ALTER TABLE ... ENABLE TRIGGER ...<\/code>):<\/p>\n<pre><code class=\"sql\">CREATE TRIGGER log\n  AFTER INSERT OR UPDATE OR DELETE\n  ON kladr\n    FOR EACH ROW\n      EXECUTE PROCEDURE diff$log();\n\nCREATE TRIGGER log\n  AFTER INSERT OR UPDATE OR DELETE\n  ON kladr_house\n    FOR EACH ROW\n      EXECUTE PROCEDURE diff$log();\n<\/code><\/pre>\n<p>\nThen we can easily extract all necessary changes from the log tables and run them through additional handlers.<\/p>\n<h3>3.3. Importing related sets<\/h3>\n<p>\nAbove, we discussed cases where the data structures of the source and receiver match. But what if the export from the external system has a format different from the storage structure in our database?<\/p>\n<p>Let's take the storage of clients and their invoices as an example, a classic \"many-to-one\" scenario:<\/p>\n<pre><code class=\"sql\">CREATE TABLE client(\n  client_id\n    serial\n      PRIMARY KEY\n, inn\n    varchar\n      UNIQUE\n, name\n    varchar\n);\n\nCREATE TABLE invoice(\n  invoice_id\n    serial\n      PRIMARY KEY\n, client_id\n    integer\n      REFERENCES client(client_id)\n, number\n    varchar\n, dt\n    date\n, sum\n    numeric(32,2)\n);<\/code><\/pre>\n<p>\nHowever, the export from the external source comes to us in an \"all-in-one\" format:<\/p>\n<pre><code class=\"sql\">CREATE TEMPORARY TABLE invoice_import(\n  client_inn\n    varchar\n, client_name\n    varchar\n, invoice_number\n    varchar\n, invoice_dt\n    date\n, invoice_sum\n    numeric(32,2)\n);<\/code><\/pre>\n<p>\nIt's clear that client data may be duplicated in this case, and the primary record is the \"invoice\":<\/p>\n<pre><code class=\"plaintext\">0123456789;Vasya;A-01;2020-03-16;1000.00\n9876543210;Petya;A-02;2020-03-16;666.00\n0123456789;Vasya;B-03;2020-03-16;9999.00\n<\/code><\/pre>\n<p>\nFor the model, we will simply insert our test data, but keep in mind \u2014 <code>COPY<\/code> more efficiently!<\/p>\n<pre><code class=\"sql\">INSERT INTO invoice_import\nVALUES\n  ('0123456789', 'Vasya', 'A-01', '2020-03-16', 1000.00)\n, ('9876543210', 'Petya', 'A-02', '2020-03-16', 666.00)\n, ('0123456789', 'Vasya', 'B-03', '2020-03-16', 9999.00);<\/code><\/pre>\n<p>\nFirst, we'll highlight the \"dimensions\" that our \"facts\" refer to. In our case, invoices refer to clients:<\/p>\n<pre><code class=\"sql\">CREATE TEMPORARY TABLE client_import AS\nSELECT DISTINCT ON(client_inn)\n-- you can just use SELECT DISTINCT if the data is known to be non-contradictory\n  client_inn inn\n, client_name \"name\"\nFROM\n  invoice_import;<\/code><\/pre>\n<p>\nTo correctly link invoices with client IDs, we first need to know or generate these identifiers. Let's add fields for them:<\/p>\n<pre><code class=\"sql\">ALTER TABLE invoice_import ADD COLUMN client_id integer;\nALTER TABLE client_import ADD COLUMN client_id integer;<\/code><\/pre>\n<p>\nWe will use the method described above for synchronizing tables with a slight adjustment \u2014 we won\u2019t update or delete anything in the target table, as client import is 'append-only':<\/p>\n<pre><code class=\"sql\">-- assigning IDs of existing records to the import table\nUPDATE\n  client_import T\nSET\n  client_id = D.client_id\nFROM\n  client D\nWHERE\n  T.inn = D.inn; -- unique key\n\n-- inserting missing records and assigning their IDs\nWITH ins AS (\n  INSERT INTO client(\n    inn\n  , name\n  )\n  SELECT\n    inn\n  , name\n  FROM\n    client_import\n  WHERE\n    client_id IS NULL -- if ID was not assigned\n  RETURNING *\n)\nUPDATE\n  client_import T\nSET\n  client_id = D.client_id\nFROM\n  ins D\nWHERE\n  T.inn = D.inn; -- unique key\n\n-- assigning client IDs to invoice records\nUPDATE\n  invoice_import T\nSET\n  client_id = D.client_id\nFROM\n  client_import D\nWHERE\n  T.client_inn = D.inn; -- natural key\n<\/code><\/pre>\n<p>\nThat's all \u2014 in <code>invoice_import<\/code> we now have the linkage field filled <code>client_id<\/code>, which we will use to insert the invoice.<br \/>\n<br \/>Source: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/492464\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u041f\u0440\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043d\u0430\u0431\u043e\u0440\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445 (\u0440\u0430\u0437\u043d\u044b\u0435 ETL-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b: \u0438\u043c\u043f\u043e\u0440\u0442\u044b, \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438 \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c) \u0447\u0430\u0441\u0442\u043e \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u0435\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u044c \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u00ab\u0437\u0430\u043f\u043e\u043c\u043d\u0438\u0442\u044c\u00bb, \u0438 \u0441\u0440\u0430\u0437\u0443 \u0431\u044b\u0441\u0442\u0440\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u043e\u0431\u044a\u0435\u043c\u043d\u043e\u0435. \u0422\u0438\u043f\u043e\u0432\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430 \u043f\u043e\u0434\u043e\u0431\u043d\u043e\u0433\u043e \u0440\u043e\u0434\u0430 \u0437\u0432\u0443\u0447\u0438\u0442 \u043e\u0431\u044b\u0447\u043d\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a: \u00ab\u0412\u043e\u0442 \u0442\u0443\u0442 \u0431\u0443\u0445\u0433\u0430\u043b\u0442\u0435\u0440\u0438\u044f \u0432\u044b\u0433\u0440\u0443\u0437\u0438\u043b\u0430 \u0438\u0437 \u043a\u043b\u0438\u0435\u043d\u0442-\u0431\u0430\u043d\u043a\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u043f\u043e\u0441\u0442\u0443\u043f\u0438\u0432\u0448\u0438\u0435 \u043e\u043f\u043b\u0430\u0442\u044b, \u043d\u0430\u0434\u043e \u0438\u0445 \u0431\u044b\u0441\u0442\u0440\u0435\u043d\u044c\u043a\u043e \u0432\u043a\u0430\u0447\u0430\u0442\u044c \u043d\u0430 \u0441\u0430\u0439\u0442 \u0438 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u043a \u0441\u0447\u0435\u0442\u0430\u043c\u00bb \u041d\u043e \u043a\u043e\u0433\u0434\u0430 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":74954,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-74953","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=\"\u041f\u0440\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043d\u0430\u0431\u043e\u0440\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445 (\u0440\u0430\u0437\u043d\u044b\u0435 ETL-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b: \u0438\u043c\u043f\u043e\u0440\u0442\u044b, \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438 \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c) \u0447\u0430\u0441\u0442\u043e.\" \/>\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\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy\" \/>\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\udd47DBA: \u0433\u0440\u0430\u043c\u043e\u0442\u043d\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0435\u043c \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u044b | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u041f\u0440\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043d\u0430\u0431\u043e\u0440\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445 (\u0440\u0430\u0437\u043d\u044b\u0435 ETL-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b: \u0438\u043c\u043f\u043e\u0440\u0442\u044b, \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438 \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c) \u0447\u0430\u0441\u0442\u043e.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy\" \/>\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-03-22T05:42:22+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-03-22T05:42:22+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\udd47DBA: effectively organizing synchronizations and imports | ProHoster","description":"When dealing with complex processing of large datasets (various ETL processes: imports, conversions, and synchronization with external sources) often.","canonical_url":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","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\udd47DBA: \u0433\u0440\u0430\u043c\u043e\u0442\u043d\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0435\u043c \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u044b | ProHoster","og:description":"\u041f\u0440\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043d\u0430\u0431\u043e\u0440\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445 (\u0440\u0430\u0437\u043d\u044b\u0435 ETL-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b: \u0438\u043c\u043f\u043e\u0440\u0442\u044b, \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438 \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c) \u0447\u0430\u0441\u0442\u043e.","og:url":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","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-03-22T05:42:22+00:00","article:modified_time":"2020-03-22T05:42:22+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"74953","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 18:04:26","updated":"2022-09-30 13:25:20","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\/74953","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=74953"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts\/74953\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/media\/74954"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/media?parent=74953"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/categories?post=74953"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/tags?post=74953"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}