Hive: Use EnvironmentContext instead of Hive Locks to provide transactional commits after HIVE-26882#6570
Conversation
5fb4c29 to
dd38a44
Compare
deniskuzZ
left a comment
There was a problem hiding this comment.
LGTM, thanks for the path Peter, that should definitely improve the Hive iceberg performance. Let me know what kind of help is needed from the Hive folks
| of the Hive Metastore (`hive.txn.timeout` or `metastore.txn.timeout` in the newer versions). Otherwise, the heartbeats on the lock (which happens during the lock checks) would end up expiring in the | ||
| Hive Metastore before the lock is retried from Iceberg. | ||
|
|
||
| Note: `iceberg.lock.hive.enabled` should only be set to `false` if [HIVE-26882](https://issues.apache.org/jira/browse/HIVE-26882) |
There was a problem hiding this comment.
Could it cause some issues in the case of HA and not consistent HMS configs across the instances?
There was a problem hiding this comment.
On HMS side HIVE-26882 is needed on both HMS nodes.
This config value is on the Iceberg side (client side from the HMS point of view).
But the question is good, because it highlights an important thing:
- All of the Iceberg writers should have the same configuration
This is the exact reason why I have added the table level configuration, so we can change the behaviour of every clients "atomically" by changing the table property.
So the upgrade process would look like this:
- Upgrade all of the clients to have the new code
- Change the table property to turn off the locking
There was a problem hiding this comment.
Do you know if there's plan to backport HIVE-26882 into previous Hive major releases, or is this only present on the newest major version (Hive 4)?
There was a problem hiding this comment.
I have backported the changes to all of the activitie Hive branches and all of the PRs have been merged:
- master/HIVE-26882: HIVE-26882: Allow transactional check of Table parameter before altering the Table hive#3888
- branch-3/HIVE-26882: HIVE-26882: Allow transactional check of Table parameter before altering the Table (#3888) (Peter Vary reviewed by Prasanth Jayachandran and Szehon Ho) hive#3943
- branch-3.1/HIVE-26882: : HIVE-26882: Allow transactional check of Table parameter before altering the Table (#3888) (Peter Vary reviewed by Prasanth Jayachandran and Szehon Ho) hive#3944
- branch-2/HIVE-26882: : HIVE-26882: Allow transactional check of Table parameter before altering the Table (#3888) (Peter Vary reviewed by Prasanth Jayachandran and Szehon Ho) hive#3946
- branch-2.1/HIVE-26882: : HIVE-26882: Allow transactional check of Table parameter before altering the Table (#3888) (Peter Vary reviewed by Prasanth Jayachandran and Szehon Ho) hive#3947
| @VisibleForTesting | ||
| HiveLock lockObject(TableMetadata metadata) { | ||
| if (hiveLockEnabled(metadata, conf)) { | ||
| return new MetastoreLock(conf, metaClients, catalogName, database, tableName); |
There was a problem hiding this comment.
prev locking strategy migrated to MetastoreLock, right?
| .add("lockState", lockState) | ||
| .toString(); | ||
| @VisibleForTesting | ||
| HiveLock lockObject(TableMetadata metadata) { |
There was a problem hiding this comment.
Could we delegate to LockManager to decide what locks need to be created (create an abstraction)? I think it would be more convenient to work in the future. WDYT?
There was a problem hiding this comment.
This is a thing I was debating a lot.
The situation is that we already have a LockManager interface which is used by other catalogs, but which is not appropriate for us. See, the PR description for more details.
IMHO if you are using HiveCatalog, you should stick to Hive for making sure that the Iceberg commit is atomic. So I do not see multiple implementation in the future for our OtherLockManager interface. Also creating another interface for LockManager would be confusing at best.
These were the reasons behind my decision, but I am not strongly convinced in any case.
Hi @deniskuzZ, good to hear from you. I hope you are ok considering... Happily, I got the approvals from @ayushtkn which were needed for backporting HIVE-26882 to Hive release branches. So the only thing remaining on Hive side is to have a release having the code available, so other projects could start using it. Thanks for the review! |
| */ | ||
| package org.apache.iceberg.hive; | ||
|
|
||
| public class NoLock implements HiveLock { |
There was a problem hiding this comment.
Based on the configuration of the Catalog and the Table the lock could be either NoLock or MetastoreLock
There was a problem hiding this comment.
Thanks for these efforts @pvary . I left some comments. I think it would be easier to review if we just did the refactor first, then the changes are more minimal for supporting the new NoLock mechanism, which I havent wrapped my mind around fully yet.
|
@szehon-ho, @RussellSpitzer: Updated after the merge of the lock refactor. |
|
@szehon-ho: Gentle reminder, could you please take a look when you have some time? |
…tional commits after HIVE-26882
6672ecb to
c54765a
Compare
| public static final boolean ENGINE_HIVE_ENABLED_DEFAULT = false; | ||
|
|
||
| public static final String HIVE_LOCK_ENABLED = "hive.lock.enabled"; | ||
| public static final boolean HIVE_LOCK_ENABLED_DEFAULT = true; |
There was a problem hiding this comment.
Could we keep the engine.hive prefix? How about engine.hive.lock-enabled?
There was a problem hiding this comment.
Interesting, I always read 'engine' as for hive-mr, ie running hive-on-iceberg, whereas this is for all engines (spark/flink) that use hive catalog.
| private ConfigProperties() {} | ||
|
|
||
| public static final String ENGINE_HIVE_ENABLED = "iceberg.engine.hive.enabled"; | ||
| public static final String LOCK_HIVE_ENABLED = "iceberg.lock.hive.enabled"; |
There was a problem hiding this comment.
This should follow the convention from table properties, like engine.hive.enabled, adding the iceberg prefix to the table property.
|
Thanks @rdblue for the review. Fixed the config keys |
szehon-ho
left a comment
There was a problem hiding this comment.
I realize its already documented, but still express concern, given how many Iceberg engines we have, how easy it is for user to un-knowingly have an old version of Iceberg that use the old mechanism to lock Hive, which will not know about this mechanism and corrupt the table. But as the concerns are already laid out, can proceed with the pr. Left some questions/comments
| * @param conf The hive configuration to use | ||
| * @return if the hive engine related values should be enabled or not | ||
| */ | ||
| private static boolean hiveLockEnabled(TableMetadata metadata, Configuration conf) { |
There was a problem hiding this comment.
Why do we want to allow override the table property using conf? I thought all writes should use the hive lock enabled, or all writers should not.
There was a problem hiding this comment.
The table property could override the catalog property.
This way there is a graceful way to enable the config for every writer at the same time:
- Start by disabling the config on table level
- Enable the writers one-by-one based on the writers release process
- When you are sure that every writer uses the new code, then you can change the table config and all of the writers start using the new locking method at the same time
There was a problem hiding this comment.
That sounds ok if its a double-check, but the way I read this code, if table config is set to NO_LOCK_ENABLED, it doesnt matter what the writer sets? So looks like table config is all that matters.
Should we improve this check to make sure both writer/table config is NO_LOCK?
There was a problem hiding this comment.
Actually I was thinking more about it, even if we were to make this check return only if both are NO_LOCK, it still doesnt address the original concern that if one writer does not have NO_LOCK but the table has NO_LOCK, then that writer will not switch to NO_LOCK and has chance to be corrupted by others that do.
So I see two options:
- all writers just follow table's NO_LOCK config
- writer could do a check, if table is set to NO_LOCK but they are not set to NO_LOCK, throw an exception and prevent the write.
There was a problem hiding this comment.
My thinking was somewhat different.
I expect that we should highlight in the documentation the possible issues, and then with the configuration provide flexibility to use it correctly.
I expect that if someone does the effort of backporting the required changes, then they have one of the following:
- They control everything and migrate with a big boom
- Few problematic tables which they need to write without locks, but they do not want to touch the other tables.
- They have a few tables which are written by old clients and most of the tables will be written by new clients
In the case of 2/3 there might be a situation where a particular client would like to write one table with locks and one table without locks, but there is a general rule which most of the clients would like to adhere.
Do I overcomplicate things? If we do not expect this kind of situation, then I would prefer a simpler/table level conf.
What do you think?
Thanks, Peter
There was a problem hiding this comment.
Sorry I'm trying but still not understanding how having a client level (catalog level) config, that is fallback to table config, allows case 2/3?
To me as it is, it seems it causes more potential problem than solves (client says NO_LOCK but table does not have NO_LOCK). Unless we want to do catalog config as a safety check (throw exception if catalog config is NO_LOCK but table does not have NO_LOCK), as I was mentioning before, which makes more sense to me.
There was a problem hiding this comment.
I think I finally understand our differences here.
- You think about Iceberg as a service, and the users as clients. In this view we have service level configurations (TableProperties), and client level configurations (CatalogProperties)
- I think about Iceberg as a library which provides different levels of configuration. The configuration provided by the developer (CatalogProperties), and global overrides which prevent configuration discrepancies between different users (TableProperties)
Does this make sense?
Am I right in understanding the different point of views?
There was a problem hiding this comment.
No I am not thinking of Iceberg like a service, but in my thought, indeed, the TableProperties are a single configuration persisted on the table, and the CatalogProperties is set by individual clients that can override.
My concern in the previous comments was, say we have one table, written by clients A and B. Table property for lock is not set.
- Client A sets catalog property LOCK_HIVE_ENABLED = "false"
- Client B has catalog property LOCK_HIVE_ENABLED = "true"
This will make clients corrupt each other commit. So was suggesting, either
- Remove catalog config altogether
- Have Client A throw exception in this case (as I was mentioning, this may be better)
Does that make sense, or am I missing something?
There was a problem hiding this comment.
Remove catalog config altogether
Then it is not possible to handle 2/3 use-cases (We either have to set the flag for every table, or rely on the default provided by the Iceberg source code - I think it is more useful for the user to provide an override possibility on catalog level too). I am open to debate the usefulness of this cases, but if we want to support them, then we need 2 levels of configuration.
Have Client A throw exception in this case (as I was mentioning, this may be better)
We can throw an exception if the Table level and the Catalog level config is contradictory. In this case every table used by a Catalog instance should have the same locking configuration if it is set at least for one of the tables used.
If we decide on this then I agree with you that there is no need to have a Catalog level configuration, as it is just for throwing exceptions in some cases.
I am starting to feel that the main difference is:
- What do we consider more important:
- Configuration flexibility: If the user wants to set all of the tables to use the new Locking mechanism, they should not need to go to every one of the tables and alter them one-by-one. A global catalog configuration should be enough in these cases. For me the question was how to handle when we would like to use a table differently than the general one and the table level config is proposed to do that.
- Preventing wrong configurations: With the "only Table level config" solution we can be sure that if every writer uses which uses a code version which contains these changes will not have a writing conflict. BTW this can be archived even with the previous proposal if all of the tables are altered one-by-one to set the Table level config.
There was a problem hiding this comment.
OK I think I get the difference as well, if I can summarize, we have two choices
- catalog config = NO_LOCK => start writing to all tables with no_lock, unless table is set explicitly to LOCK.
- catalog config = NO_LOCK => start writing to tables with no_lock only if table set explicitly to NO_LOCK.
In both cases, we can achieve case 2/3, the issue is whether the user has to set the tables explicitly to exclude or include them in new system.
You are right that maybe Option 2 is too much of a burden for the user to manually mark all tables as NO_LOCK. If all Iceberg clients are upgraded in the system, Option 2 may be a good check as then we are sure all of them will write NO_LOCK together, but we come back to the same problem if some clients are not upgraded. (which is what case 2/3 is trying to solve).
Let me think about it, but I think you are right that we cannot do better in the mixed-version scenarios.
| public static final boolean ENGINE_HIVE_ENABLED_DEFAULT = false; | ||
|
|
||
| public static final String HIVE_LOCK_ENABLED = "hive.lock.enabled"; | ||
| public static final boolean HIVE_LOCK_ENABLED_DEFAULT = true; |
There was a problem hiding this comment.
Interesting, I always read 'engine' as for hive-mr, ie running hive-on-iceberg, whereas this is for all engines (spark/flink) that use hive catalog.
| * @param conf The hive configuration to use | ||
| * @return if the hive engine related values should be enabled or not | ||
| */ | ||
| private static boolean hiveLockEnabled(TableMetadata metadata, Configuration conf) { |
There was a problem hiding this comment.
Also, is there any way to validate Hive compaitibility, to prevent old Hive version from disabling Hive locks?
There was a problem hiding this comment.
Since there is no official release with the new code yet, sadly this is not an option 😢
There was a problem hiding this comment.
It would be great if we could at least protect the user from this potential corruption error (running without the patch HIVE-26882)
General question: is there any way we can check Hive server version? Would be nice in general.
There was a problem hiding this comment.
It is.
So once it get released, we can check the version.
There was a problem hiding this comment.
Great, I think this would be very important to get in for users of OSS Hive, its a bit hacky otherwise.
There was a problem hiding this comment.
Let's make an issue to track it on Iceberg side
| Map<String, String> env = Maps.newHashMapWithExpectedSize(extraEnv.size() + 1); | ||
| env.putAll(extraEnv); | ||
| env.put(StatsSetupConst.DO_NOT_UPDATE_STATS, StatsSetupConst.TRUE); | ||
|
|
There was a problem hiding this comment.
Checking, if the version of alter_table does not support env context, it will throw an exception instead of silently fail?
There was a problem hiding this comment.
Sadly no 😢 . Historical reasons again
There was a problem hiding this comment.
Yea I was thinking, can we throw a ValidationException here then, if the API is to pass in env context, and the alter_table method that we load does not take it?
There was a problem hiding this comment.
We can not distinguish between the 2 cases. The API is backwards compatible (as expected by the Hive team), so the HMS will always take the env context input variables, but will not act on them.
I think this is not that interesting case, as the writer should have a clear info about the HMS they are connecting to.
There was a problem hiding this comment.
Oh maybe I was not clear, I meant the alter_method that we load dynamically: https://github.com/apache/iceberg/blob/master/hive-metastore/src/main/java/org/apache/iceberg/hive/MetastoreUtil.java#L29 . Original question was just asking: if our method object is the one loaded without env, and we pass in env here, what will happen? (is the error message good enough for user to decipher, and it wont silently pass?) Its a pretty rare scenario, just wondering.
There was a problem hiding this comment.
This method is used in other cases as well and doing some extra checks would be edgy.
Added a check for the NoLock constructor to ensure that at least Hive 2 client is used when a NoLock object is created. This ensures that the correct client is used in this case.
There was a problem hiding this comment.
Hey sorry , I thought I commented on this, but must have missed it. I was thinking something like:
if (env.size() > 0) {
Preconditions.checkArgument("Environment context is non-empty but alter_table method does not support it", ALTER_TABLE.args().size() == 5);
}
What do you think? Isn't alter_table only called with envContext is the new code?
There was a problem hiding this comment.
We always add ImmutableMap.of(StatsSetupConst.DO_NOT_UPDATE_STATS, StatsSetupConst.TRUE) which is an optimisation for skipping stats generation / file listing when it is not needed on alter table commands. This always depended on DynMethod skipping last parameters (maybe by accident)
See:
We can modify the proposed command to:
if (env.size() > 1) {
Preconditions.checkArgument("Environment context is non-empty but alter_table method does not support it",
ALTER_TABLE.args().size() == 5);
}
Or add a new check to the the new alterTable method, but I find it better / more readable to add the check to the NoLock constructor.
WDYT?
There was a problem hiding this comment.
Yea I was thinking at the very beginning of the method, before we put the ALTER_TABLE? Yea it may be better for understanding of the code if we have both checks, but you are right it's not a big deal in practice, just in the rare case there's a patched version of Hive 2 out there that has alter_table without 5 arguments.
There was a problem hiding this comment.
Checked, and every Hive 2 should have the required method on the HMS API:
https://github.com/apache/hive/blob/release-2.0.0/metastore/if/hive_metastore.thrift
So we are good here
| Hive Metastore before the lock is retried from Iceberg. | ||
|
|
||
| Note: `iceberg.engine.hive.lock-enabled` should only be set to `false` if [HIVE-26882](https://issues.apache.org/jira/browse/HIVE-26882) | ||
| is available on the Hive Metastore server and every writer of a given table is using Iceberg 1.2 or later. |
There was a problem hiding this comment.
We should change this to warn. We also miss warning about some catalogs setting true and others setting false.
How about this to add some details. I use HiveCatalog here, even though its not defined in the doc, otherwise its quite lengthy to continue the language- "catalog using Hive Metastore connector"
Warn: Setting iceberg.engine.hive.lock-enabled=false will cause HiveCatalog to commit to tables without using Hive locks. This should only be set to false if all following conditions are met:
- HIVE-26882
is available on the Hive Metastore server - All other HiveCatalogs committing to tables that this HiveCatalog commits to are also on Iceberg 1.3 or later
- All other HiveCatalogs committing to tables that this HiveCatalog commits to have also disabled Hive locks on commit.
Failing to ensure these conditions risks corrupting the table.
Even with iceberg.engine.hive.lock-enabled set to false, a HiveCatalog can still use locks for individual tables by setting the table property 'engine.hive.lock-enabled'='true'. This is useful in the case where other HiveCatalogs cannot be upgraded and set to commit without using Hive locks.
There was a problem hiding this comment.
Done.
Added extra highlights for the warning Failing to ensure these conditions risks corrupting the table.
| Map<String, String> env = Maps.newHashMapWithExpectedSize(extraEnv.size() + 1); | ||
| env.putAll(extraEnv); | ||
| env.put(StatsSetupConst.DO_NOT_UPDATE_STATS, StatsSetupConst.TRUE); | ||
|
|
There was a problem hiding this comment.
I was looking through DynMethod, there is no check and will just pass whatever args it can fit. I think it will be nice to add a 'argLength' method DynMethod and then assert that argLength == 5 here, so ensure the env is not silently dropped.
|
Hey thanks, I had two additional comments for consideration, otherwise it looks good to me. |
|
Thanks for the review @deniskuzZ, @rdblue, @haizhou-zhao! |
…tional commits after HIVE-26882 (apache#6570)
|
https://issues.apache.org/jira/browse/HIVE-26882 has something wrong, should we has some documentation to info user? |
|
@chenwyi2: Maybe we should wait a bit and see if we have a fix and document that. |
|
"Minimally Hive 2 HMS client is needed to use HIVE-26882 based locking" why we have to check Hive 2? Suppose if i am in hive 1 and i cherry pick HIVE-26882, that will not be right? |
|
@chenwyi2: If you backport the changes to Hive 1, then you can use the feature. I suggest to create your own release for Iceberg as well. |
HIVE-26882 gives us the possibility to atomically change the
metadata_locationusing a singlealter_tableHMS call. The change is a new feature, so exception is needed from the Hive community to include it to new releases. OTOH I deliberately kept simple, so if someone uses their own Hive release they could backport the change easily.If we start using this possibility we can avoid using HMS locks to ensure the atomicity of the
HiveTableOperations.commit. This has the following benefits:The solution has 2 parts:
Some of my concerns and thoughts:
LockManagerinterface defined in theiceberg-apimodule. After some back-and-forth I decided against using it, because of the following reasons:setConfmethod is called on the HiveCatalogensureActiveto the interface which is needed for HiveTableOperationsBaseLockManagerdoes not provide too much of a functionalityLockManagerimplementationsWDYT?
Open for comments and suggestions.
Thanks,
Peter