Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kfkYf-0007rL-2B for pgsql-hackers@arkaria.postgresql.org; Thu, 19 Nov 2020 14:05:09 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1kfkYe-00045j-0Y for pgsql-hackers@arkaria.postgresql.org; Thu, 19 Nov 2020 14:05:08 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kfkYd-00045c-Hc for pgsql-hackers@lists.postgresql.org; Thu, 19 Nov 2020 14:05:07 +0000 Received: from mail-ed1-x541.google.com ([2a00:1450:4864:20::541]) by makus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.92) (envelope-from ) id 1kfkYa-0004Qn-Lq for pgsql-hackers@postgresql.org; Thu, 19 Nov 2020 14:05:06 +0000 Received: by mail-ed1-x541.google.com with SMTP id k4so5973278edl.0 for ; Thu, 19 Nov 2020 06:05:04 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=enterprisedb-com.20150623.gappssmtp.com; s=20150623; h=subject:to:cc:references:from:message-id:date:user-agent :mime-version:in-reply-to:content-language:content-transfer-encoding; bh=4M9xxYJu2qRlqdSws71/8mTlB0TSCJA7wgS7PsLYDA4=; b=tPKcm84B9F/wMXrlodLu/N899Dfz7sqvp97kp+eagulxvyNZhxixKtTZOYPNcrKZzY WRUmuiLY27c2O2Xe+/IGZ6YSlpaw91PuYXinETXVn4RXr6v159bRfwmwE6JjRBps72l5 KBCxwXSjc7ogEsgEzoRDfDCDOpJWGGrxXAB5tX9b5VsSKAg/fux5qSqGLutWvpUP9NnQ L6zH3Nj6lV04u3O4RBMz2W21xINHHE3E3aTgatjo8X6Yexba/8c4JEwxEnQSZDh2c9Xy lmwuk7/YgbjfNAkknmcQXH1ouNdAyCjcF6glVvzfUFPsQqx/QdrqOd3G2v8y3p4rqB6S rmTg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:subject:to:cc:references:from:message-id:date :user-agent:mime-version:in-reply-to:content-language :content-transfer-encoding; bh=4M9xxYJu2qRlqdSws71/8mTlB0TSCJA7wgS7PsLYDA4=; b=QUI377v/FNVqO3SGVPlvbcoVjX9jYi7LA1pJE9Hui5RWacsWWQ5Q0M4qiY3+OKLEWY +OGgyQQVlVY9KShZedB7wFf6ejiFgrsbbfBHlTLoqG8jxKvWr6kQ9B77KPTQPwN5IKs1 sNSAVJbmE92WKcoQXETMTF3LQ5nCwGtxv1UbrFcHve+vGP2GAzLk8IPkJ32Jnamybrjn kbr4qbEVxL4dP0J2ksONIcWoMD1YDgPtHHq+eZPWv1GxYQRqcePRZqIIHKpWnBlaZV5f 7qi28I4ORqjHhyoWQHDhIVIzj8HP62uP4cjAOuU9KWqP3TLEFYIl/1EUlqgHLxugfDwK u6GQ== X-Gm-Message-State: AOAM530kaaI5eFsOn8GzFsCJGsJxfvAHcwjCluyHvLClP91cuF+3Uv5R Oy1sACvDuKndK6NndDVSRWK/MTyf9VlvTT336cRxJwVXYwFQ9iy7yuzmnhPZDfT6UUbsGFkEfh+ GMWP+UmDOabHHlt4ayRUCCm7mH4YaoEnp0zozToBEXumnvgALjGPWR8oCSqYNwAz37twV2901he 8NSoAjSkDgLlO3AM7h7EPtcuSDYQuXT6Oa7KXeCmzeJgNzGQKe7Q0391H3jFqIcyei1uOuVq3h4 rYaeZCm2I/QjuzqTP46BRhQ+QI29F9ilHx3msUb X-Google-Smtp-Source: ABdhPJyRW2MuOjhdUIUMalIz+sNIl5HBKlzk8DJSeJqdILCvOGr3Bt2/YkiW/Ddwxp2Tg1bwffH9Xg== X-Received: by 2002:aa7:c5c7:: with SMTP id h7mr6719050eds.143.1605794702569; Thu, 19 Nov 2020 06:05:02 -0800 (PST) Received: from [10.137.0.18] (ip-86-49-245-76.net.upcbroadband.cz. [86.49.245.76]) by smtp.gmail.com with ESMTPSA id x20sm14560369ejv.66.2020.11.19.06.05.01 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Thu, 19 Nov 2020 06:05:01 -0800 (PST) Subject: Re: POC: postgres_fdw insert batching To: "tsunakawa.takay@fujitsu.com" Cc: 'Tomas Vondra' , "Andrey V. Lepikhov" , "pgsql-hackers@postgresql.org" References: <20200628151002.7x5laxwpgvkyiu3q@development> <0300d7df-854b-6f72-1e93-dfbf98f3fc8a@postgrespro.ru> <20200712001101.x65f3hvsanvamtgj@development> <20201008221421.foasnh5of66rupnm@development> From: Tomas Vondra Message-ID: <71113ab1-ed31-3dbc-ce07-e081e17473b6@enterprisedb.com> Date: Thu, 19 Nov 2020 15:04:59 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.4.0 MIME-Version: 1.0 In-Reply-To: Content-Type: text/plain; charset=utf-8 Content-Language: en-US Content-Transfer-Encoding: 7bit X-CLOUD-SEC-AV-Info: enterprisedb,google_mail,monitor X-CLOUD-SEC-AV-Sent: true X-Gm-Spam: 0 X-Gm-Phishy: 0 List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk On 11/19/20 3:43 AM, tsunakawa.takay@fujitsu.com wrote: > From: Tomas Vondra >> Unfortunately, this does not compile for me, because >> nodeModifyTable calls ExecGetTouchedPartitions, which is not >> defined anywhere. Not sure what's that about, so I simply >> commented-out this. That probably fails the partitioned cases, but >> it allowed me to do some review and testing. > > Ouch, sorry. I'm ashamed to have forgotten including > execPartition.c. > No reason to feel ashamed. Mistakes do happen from time to time. > >> The are a couple other smaller changes. E.g. it undoes changes to >> finish_foreign_modify, and instead calls separate functions to >> prepare the bulk statement. It also adds list_make5/list_make6 >> macros, so as to not have to do strange stuff with the parameter >> lists. > > Thanks, I'll take them thankfully! I wonder why I didn't think of > separating deallocate_query() from finish_foreign_modify() ... > perhaps my brain was dying. As for list_make5/6(), I saw your first > patch avoid adding them, so I thought you found them ugly (and I felt > so, too.) But thinking now, there's no reason to hesitate it. > I think it's often easier to look changes like deallocate_query with a bit of distance, not while hacking on the patch and just trying to make it work somehow. For the list_make# stuff, I think I've decided to do the simplest thing possible in extension, without having to recompile the server. But I think for a proper patch it's better to keep it more readable. > ... > >> 1) As I mentioned before, I really don't think we should be doing >> deparsing in execute_foreign_modify - that's something that should >> happen earlier, and should be in a deparse.c function. > ... >> The attached patch tries to address both of these points. >> >> Firstly, it adds a new deparseBulkInsertSql function, that builds a >> query for the "full" batch, and then uses those two queries - when >> we get a full batch we use the bulk query, otherwise we use the >> single-row query in a loop. IMO this is cleaner than deparsing >> queries ad hoc in the execute_foreign_modify. > ... >> Of course, this might be worse when we don't have a full batch, >> e.g. for a query that insert only 50 rows with batch_size=100. If >> this case is common, one option would be lowering the batch_size >> accordingly. If we really want to improve this case too, I suggest >> we pass more info than just a position of the VALUES clause - that >> seems a bit too hackish. > ... >> Secondly, it adds the batch_size option to server/foreign table, >> and uses that. This is not complete, though. >> postgresPlanForeignModify currently passes a hard-coded value at >> the moment, it needs to lookup the correct value for the >> server/table from RelOptInfo or something. And I suppose >> ModifyTable inftractructure will need to determine the value in >> order to pass the correct number of slots to the FDW API. > > I can sort of understand your feeling, but I'd like to reconstruct > the query and prepare it in execute_foreign_modify() because: > > * Some of our customers use bulk insert in ECPG (INSERT ... > VALUES(record1, (record2), ...) to insert variable number of records > per query. (Oracle's Pro*C has such a feature.) So, I want to be > prepared to enable such a thing with FDW. > > * The number of records to insert is not known during planning (in > general), so it feels natural to get prepared during execution phase, > or not unnatural at least. > I think we should differentiate between "deparsing" and "preparing". > * I wanted to avoid the overhead of building the full query string > for 100-record insert statement during query planning, because it may > be a bit costly for usual 1-record inserts. (The overhead may be > hidden behind the high communication cost of postgres_fdw, though.) > Hmm, ok. I haven't tried how expensive that would be, but my assumption was it's much cheaper than the latency we save. But maybe I'm wrong. > So, in terms of code cleanness, how about moving my code for > rebuilding query string from execute_foreign_modify() to some new > function in deparse.c? > That might work, yeah. I suggest we do this: 1) try to use the same approach for both single-row inserts and larger batches, to not have a lot of different branches 2) modify deparseInsertSql to produce not the "final" query but some intermediate representation useful to generate queries inserting arbitrary number of rows 3) in execute_foreign_modify remember the last number of rows, and only rebuild/replan the query when it changes > >> 2) I think the GUC should be replaced with an server/table option, >> similar to fetch_size. > > Hmm, batch_size differs from fetch_size. fetch_size is a > postgres_fdw-specific feature with no relevant FDW routine, while > batch_size is a configuration parameter for all FDWs that implement > ExecForeignBulkInsert(). The ideas I can think of are: > > 1. Follow JDBC/ODBC and add standard FDW properties. For example, > the JDBC standard defines standard connection pool properties such as > maxPoolSize and minPoolSize. JDBC drivers have to provide them with > those defined names. Likewise, the FDW interface requires FDW > implementors to handle the foreign server option name > "max_bulk_insert_tuples" if he/she wants to provide bulk insert > feature and implement ExecForeignBulkInsert(). The core executor > gets that setting from the FDW by calling a new FDW routine like > GetMaxBulkInsertTuples(). Sigh... > > 2. Add a new max_bulk_insert_tuples reloption to CREATE/ALTER FOREIGN > TABLE. executor gets the value from Relation and uses it. (But is > this a table-specific configuration? I don't think so, sigh...) > I do agree there's a difference between fetch_size and batch_size. For fetch_size, it's internal to postgres_fdw - no external code needs to know about it. For batch_size that's not the case, the ModifyTable core code needs to be aware of that. That means the "batch_size" is becoming part of the API, and IMO the way to do that is by exposing it as an explicit API method. So +1 to add something like GetMaxBulkInsertTuples. It still needs to be configurable at the server/table level, though. The new API method should only inform ModifyTable about the final max batch size the FDW decided to use. > 3. Adopt the current USERSET GUC max_bulk_insert_tuples. I think > this is enough because the user can change the setting per session, > application, and database. > I don't think this is usable in practice, because a single session may be using multiple FDW servers, with different implementations, latency to the data nodes, etc. It's unlikely a single GUC value will be suitable for all of them. regards -- Tomas Vondra EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company