-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add initial PostgreSQL implementation for get & put operations #5
Conversation
app/src/main/java/org/vss/impl/postgres/sql/v0_create_vss_db.sql
Outdated
Show resolved
Hide resolved
app/build.gradle
Outdated
generateSchemaSourceOnCompilation = true | ||
|
||
generationTool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need to make a decision if we check in the generated Java code or not. personally, I'm a bit uncomfortable with generating code on the fly on prod machines, but let's see what other people think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed this offline. Prefer to check in the code but also provide a way for users to generate it at as an option to the build process.
app/src/main/java/org/vss/impl/postgres/PostgresBackendImpl.java
Outdated
Show resolved
Hide resolved
|
||
VssDbRecord globalVersionRecord = buildVssRecord(storeId, | ||
KeyValue.newBuilder() | ||
.setKey(GLOBAL_VERSION_KEY) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the global version should be stored in a separate table. otherwise, you have to make sure the caller doesn't try to use this key, and it seems messy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The advantage is that it can be treated as any other keyvalue and easily handled with exactly same cases.
Other consideration is, if we want client to be able to override global_version in certain scenarios. My take is we should allow, end of the day its their storage and versioning, they can do whatever they want with it.
There is also case where client just wants to do a "get" on global_version.
For all purposes, it seems simpler to keep it as normal key-value, otherwise i will need to introduce additional code to support everything.
One case that we will have to handle separately from normal key-value is to not return global_version as part of ListKeyVersions api. (we have dedicated field in response for it)
app/src/main/java/org/vss/impl/postgres/PostgresBackendImpl.java
Outdated
Show resolved
Hide resolved
app/src/main/java/org/vss/impl/postgres/PostgresBackendImpl.java
Outdated
Show resolved
Hide resolved
app/src/main/java/org/vss/impl/postgres/PostgresBackendImpl.java
Outdated
Show resolved
Hide resolved
also, would be good to have an integration test. not sure if it's better to have it against postgres (more precise model of a production environment) or against a lightweight in-memory SQL DB (faster to run the tests). |
.setKey(GLOBAL_VERSION_KEY) | ||
.setVersion(request.getGlobalVersion()) | ||
.build()); | ||
if (request.hasGlobalVersion()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't increment the global version when it's not specified in the request. we always want to increment it, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we could have the specifications say that it doesn't get incremented if not present in the request, but we should be explicit about it, since the developer might not expect that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on how optimistic locking works, version needs to be provided by client in order to enact it.
So expectation is that we don't increment it and dont perform the check if developer doesn't supply it.
If global_version is not supplied, it means it is a non-global-version-check-required write.
We shouldn't be incrementing global_version in this case as client-side will not increment and has no way of knowing this without performing a sync.
Based on different application needs, some might not need a global_version check on every write, and this feature is meant to support those applications.
Added an integration test for AbstractKVStore and PostgresIntegration test uses it. |
postgreSQLContainer.getUsername(), postgreSQLContainer.getPassword()); | ||
DSLContext dslContext = DSL.using(conn, SQLDialect.POSTGRES); | ||
|
||
this.kvStore = new PostgresBackendImpl(dslContext); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't we need to close the previous DB connection, otherwise we may run out of file descriptors or such if we have many tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Each and every test spins up new db cluster/instance so db connection shouldn't really be a problem.
But I added a AfterEach block to destroy connection in any case.
For production, we are using connection pool and DSL.using(dataSource, dialect) where jooq/pool does the connection management for us.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to get through the tests still
app/build.gradle
Outdated
generateSchemaSourceOnCompilation = true | ||
|
||
generationTool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed this offline. Prefer to check in the code but also provide a way for users to generate it at as an option to the build process.
|
||
ListKeyVersionsResponse listKeyVersions(ListKeyVersionsRequest request); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: no blanks at end of file here and throughout
@@ -0,0 +1,8 @@ | |||
CREATE TABLE vss_db ( | |||
store_id character varying(120) NOT NULL, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a convention to have two spaces before NULL
/ NOT NULL
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
idk, just a copy-paste side-effect from describe table in postgres i guess, will replace with single spaces.
store_id character varying(120) NOT NULL, | ||
key character varying(120) NOT NULL, | ||
value bytea NULL, | ||
version bigint NOT NULL, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was the choice of using a signed integer in the vss.proto
based on the fact the database doesn't support unsigned types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly because many languages(kotlin/java/python) don't have good support for unsigned values.
So we avoid using them directly in interface if possible. (had this feedback from cashapp as well)
dslContext.execute("CREATE TABLE vss_db (" | ||
+ "store_id character varying(120) NOT NULL CHECK (store_id <> '')," | ||
+ "key character varying(120) NOT NULL," | ||
+ "value bytea NULL," | ||
+ "version bigint NOT NULL," | ||
+ "PRIMARY KEY (store_id, key));"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to take this from the sql file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that the sql file is present only for documentation purpose and git history.
Its not going to be directly used in db creation, will need to be done manually.
I wanted to avoid a filepath dependency in tests, let me know your thoughts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could creation be accomplished through a script / program that reads the schema?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jooq doesn't create table for us and we assume in production that db schema is already created.
In tests however we are creating a fresh instance of db for every test and need to create table/schema everytime.
|
||
private void createTable(DSLContext dslContext) { | ||
dslContext.execute("CREATE TABLE vss_db (" | ||
+ "store_id character varying(120) NOT NULL CHECK (store_id <> '')," |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this line differ from the sql file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will fix this.
} else { | ||
keyValue = KeyValue.newBuilder() | ||
.setKey(request.getKey()).build(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we return an error if there is no record for the key?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think an empty response is preferable in this case.
ResourceNotFound or 404 might represent a myriad of issues and things going wrong. (url wrong etc.)
In this case, we consider it a perfectly valid request to get a key which does not exist or to check existence of a key.
I see no harm in mixing keys that exist with no data and keys which don't exist, to make clients life easier instead of throwing an exception in one case.
Note: This is only applicable if client has permission to storeId, if not then we would want to throw ResourceNotFound.
However, this is a controversial topic and there are reasons to go either way.
dslContext.execute("CREATE TABLE vss_db (" | ||
+ "store_id character varying(120) NOT NULL CHECK (store_id <> '')," | ||
+ "key character varying(120) NOT NULL," | ||
+ "value bytea NULL," | ||
+ "version bigint NOT NULL," | ||
+ "PRIMARY KEY (store_id, key));"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could creation be accomplished through a script / program that reads the schema?
KeyValue response = getObject("non_existent_key"); | ||
|
||
assertThat(response.getKey(), is("non_existent_key")); | ||
assertTrue(response.getValue().isEmpty()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also check that version is empty / zero?
int[] batchResult = dsl.batch(batchQueries).execute(); | ||
|
||
for (int numOfRowsUpdated : batchResult) { | ||
if (numOfRowsUpdated == 0) { | ||
throw new ConflictException( | ||
"Transaction could not be completed due to a possible conflict"); | ||
} | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you test the case where one key failing causes the entire transaction to fail? (i.e., the successful key is not updated)
68a6729
to
344779e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like some of the comments for the first commit were resolved in the second commit. You'll want to make sure they are resolved in the right commit so that they are self-contained. Otherwise, looks good and sorry about the delay.
PRIMARY KEY (store_id, key) | ||
); | ||
|
||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add newline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I earlier got comment about removing newlines/blanks at end of files,
bit confused, let me know if i misunderstood.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, please disregard. I thought that Github was showing the "No newline at end of file" symbol, but I guess I was mistaken.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, i might have fixed it,
So just to clarify there "should" be a newline at EOF?
Addressed other comments as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, it should contain a newline, just not an empty line.
344779e
to
403b0c2
Compare
TestStrategy
How?