-
Notifications
You must be signed in to change notification settings - Fork 921
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
Switch to TIMESTAMP WITH TIME ZONE for current_* tables #375
Comments
Isn't the answer to fix the rails/cgimap code to use a UTC timestamp when creating data in the database? |
My understanding is if you want everything in UTC like you generally do in OSM, you want WITH TIME ZONE and set the the time zone to UTC. I have to admit I don't exactly understand all the issues with time zones, I'm just going based on advice. |
As all the data in our database is in UTC it is not clear to me why we need to store a time zone - that should only be needed when you have data in a mix of time zones so that you can correctly compare them and do calculations involving mixed time zones. You're going to need a much more convincing argument to get a massive change like this through. |
First thing first: there is no storage overhead for the 'with timezone' variant, as postgres always stores in UTC. The only (tiny) overhead is the on-the-fly timezone conversion if the client needs it. The advantage of using 'with timezone' is to fend off client bugs. If the client is smart, he'll know to expect UTC (or whatever is used by convention), but experience shows that clueless clients will happen. At work I've had a few instances of having to force a client timezone (by editing the db user's properties) to work around client bugs. To avoid drama, the database driver or proxy can inform the server what is the client-expected timezone. That said, if you are dead sure that all osm clients know that they are geting UTC from the db and do the conversion-before-printing work themselves, you can keep using 'without timezone'. But for such a distributed project as osm, it's probably better to play it safe. |
Well, we know that some parts of the OSM stack get it wrong because we've already got an osmosis bug about the matter. OSM data has a time zone associated with it, it just happens to all be the same. I don't feel this is actually a substantial change as it shouldn't require changes for anything which currently is behaving correctly. @vincentdephily How'd you hack around client bugs? That might work for me in the short term for getting around the data loading bugs |
@pnorman Assuming there is a db role dedicated to each client/program (which is good practice): "ALTER ROLE buggy_app SET timezone to 'Europe/Paris'". This works in the case of a client which expects a specific timezone but doesn't tell the server, which therefore returns a different timezone instead. Obviously (?), this hack only works if the data is in a 'with timezone' column. The default timezone depends on various server/client/os settings and should not be trusted. The correct way to handle this is to configure the client code (or its framework) to require a specific timezone when connecting to PG. |
In my opinion the best option would be to fix the actual bugs. I've looked through the source very briefly and couldn't find anywhere we weren't converting to UTC. The issue may be something along the lines that Postgres (or the client library) is assuming "without timezone" means "in local timezone", in which case there should be a much simpler fix than converting to "with timezone". A simple work-around may be to set $TZ when running Osmosis. Anecdotally, my experience of writing systems with timestamp information is that it's always easier to keep data internally in UTC at all times and only convert when presenting data to the user. In OSM, I don't think we ever convert out of UTC - all API-generated timestamps should be UTC, and uploaded timestamps are ignored. In my opinion, timestamp and timezone information are qualitatively very different: the timestamp indicates a particular point in time which is unambiguous to a computer, whereas timezones are only useful as a presentation mechanism to a human. The prevalence of |
@pnorman it is a substantial change if only because it requires an "ALTER COLUMN" to be performed on many of our largest tables. |
I believe that's what without timezone is defined as in the SQL standard
I also need to try inserting by hand to see what results I get
Well, the in-database timestamps aren't in UTC because they're not in any timezone.
But in SQL a timestamp without timezone is ambiguous to a computer because it could be in any time zone. When I first read it awhile back the SQL definitions seemed flipped from what I expected in some cases.
Good point, the time required may be non-trivial with the table rewrite, even if the change itself is simple. |
Indeed - the timestamps aren't in a definite timezone as far as the database is concerned (except that it assumes some for conversions from "with timezone") - my meaning was that we define all timestamps to be in UTC, and always internally work in UTC as if we're using The Postgres DATETIME docs say "a literal that has been determined to be timestamp without time zone, PostgreSQL will silently ignore any time zone indication." So the next question is whether the client performs any implicit conversion, or passes the information as "with timezone", forcing a conversion.
Sure, which is why it's easier to define that we always only store UTC timestamps, and we don't (shouldn't) perform any conversions in the database or in the database clients. Timezone information, if useful at all, should only be used in the presentation layer to the user. |
The server just replies in a "YYYY/MM/DD HH:MM:SS.nnn" format that has no
That's a good quickfix, but means that the whole of osmosis will run with A cleaner option is to use the AT TIME ZONE construct when running an sql query: SELECT date AT TIME ZONE 'utc' from current_foobar; This will tell PG that the 'without timezone' value is stored in UTC. The It's not as clean a solution as altering the db schema (because it requires
tokill=# show time zone;
Europe/Paris
tokill=# \d foo
d_notz | timestamp without time zone |
d_tz | timestamp with time zone |
tokill=# insert into foo values ('2013/01/01 00:00:00', '2013/01/01 00:00:00'),('2013/01/01 00:00:00', '2013/01/01 00:00:00' at time zone 'utc');
INSERT 0 2
tokill=# select d_notz, d_notz at time zone 'utc', d_tz from foo;
2013-01-01 00:00:00 | 2013-01-01 01:00:00+01 | 2013-01-01 00:00:00+01
2013-01-01 00:00:00 | 2013-01-01 01:00:00+01 | 2012-12-31 23:00:00+01 |
The server's timestamps are actually in UTC. Therefore it seems the cleanest solution is fixing the bugs in the client where they are assuming, incorrectly, that they're in local time.
This looks to me like the |
The 'with timezone' variant is the idiomatic way to fix timezone issues. It was created for this purpose and is supported all the way down to the wirelevel protocol. You can fix timezone issues another way, but that's IMHO just a hack. The 'without timezone' variants aren't lighter than the 'with' ones; they exist mainly for sql compliance / backward compatibility and there's no good reason to use them nowadays.
You're hoping to outsmart the postgres devs, it's rarely a good bet :p You can either fix the timezone issue once server-side following best-practice, or multiple times (once per client) using one of the solutions above. That being said, if the "alter table" downtime is unacceptable to the sysadmins, then that theoretical discussion is moot, and the practical decision to fix things client-side wins. |
My experience is exactly the opposite: timestamps are
I'm not trying to outsmart anyone - I'm just trying to avoid unnecessary complication and leakage of presentation-layer concepts into the database. But sadly these appear to be mandated by the SQL standard 😟. We have already fixed the server, but unfortunately the database client libraries are apparently still broken by design. |
Consider these facts :
The complexity is already there wether you want it or not, it's not an overengineering by PG or SQL. It's not unecessary. It's not just a presentation-layer concept. Once again, you can either fix the issue once server-side, or many times client-side (have fun implementing group-by clientside, btw). Thanks to SQL and 'with timezone', the common case is that the client should be oblivious of the timezone. That's a good thing: it means less code and less bugs. To reuse your own argument, the problem is with client that try to do their own timezone conversion in the first place. Don't fall into the trap of treating PG as a "dumb datastore" or you'll just waste time reinventing the wheel. |
via #postgresql, while thinking about pnorman/openstreetmap-api-testsuite#1
Yes - I think it would screw up progress displays at the least
Well, the output OSM XML is all in UTC in the format For cgimap how it gets this is it converts to a string in postgresql, see https://github.com/zerebubuth/openstreetmap-cgimap/blob/8bfff6/src/backend/apidb/writeable_pgsql_selection.cpp#L399 which does to_char(n.timestamp,'YYYY-MM-DD"T"HH24:MI:SS"Z"') |
This issue has come up again in #2028 and I feel it's something that we need to resolve. Unfortunately there's a few misunderstandings from @tomhughes and @zerebubuth on this topic, so I hope to persuade them of the case for changing the column type. http://www.postgresqltutorial.com/postgresql-timestamp/ has some background reading. The main misunderstanding appears to be that
The implications of the change for OSMF are low, since they run all their servers in UTC. But the implications for anyone else running the software, for example a developer on a laptop in another timezone, is that they are likely to get a messed up timestamps in their db. Values that should be UTC (e.g in a dump) are stored as local times instead. Changing the column type to properly reflect that the timestamps are UTC and not local time will remove a whole lot of head-scratching. So to summarise, we are currently using |
This matches what I have heard from 2-4 PostgreSQL consultants who I have asked about the matter. In summary, their advice is "Unless you are developing calendar software, never use |
This was raised in #2028 (comment) If everything involve is using UTC, there's no cgimap issue. Where I've hit the issue has been on a dev machine where it's not UTC, and in that case, it's got a good chance of being screwed up. |
@pnorman exactly. The only reason to use it is for events that you want to happen in local time. So you could store an event "worldwide morning yoga sessions" at "2018-11-21T0900" I really do think this is all a misinterpretation based on what people think the column types mean, based on the names of the types, rather than what they actually mean. I can understand the misinterpretation completely, it's not clearly expressed. |
Surely "with timezone" has to store the timezone? The actual time might be normalised to UTC but it has to store the original timezone as well so it can return it. That's the whole point right? Or have I completely misunderstood SQL for years... Time to do some reading I guess. |
No, that's not how it works! :-) There's no offset stored, the information about the original timezone is ignored. If you store somethings as ".....10:05Z+0800" into a https://stackoverflow.com/a/9576170/105451 is another good article. It notes "The time zone itself is never stored. It is an input modifier used to compute the according UTC timestamp, which is stored - or and output modifier used to compute the local time to display - with appended time zone offset." |
So it seems you're right which is very, err, counter-intuitive. My understanding had always been with a Apparently that is not true for Postgres at least, and it always returns values in the servers default timezone. My only remaining concern is that timestamp constants are always treated as |
I think this is also a point where different databases are not consistent - my reading is that Oracle for example does do what I expected but also has an extra |
Do we use timestamp constants anywhere? I'm guessing not in our rails code, but perhaps in other apps? If so, they could be re-written to be a |
I have no idea whether we do (or whether rails might behind the scenes) it's just something to keep in mind. |
This is all from https://www.postgresql.org/docs/10/datatype-datetime.html#id-1.5.7.13.19.7 which suggests I think it only matters if there is a TZ indication in the constant though so probably unlikely to ever be an issue. |
Noted. My remaining query is on the time taken to make the column type changes in production, i.e. will we need any downtime to run the migration, or given it should (hopefully) be just a column metadata change, is it quick? Is there an easy way to find this out before we do it for real? |
That's a good question, especially in 9.5, so we need to find a way to check it. We'd likely need a brief outage anyway to avoid lock contention issues on some of the tables at least but we'd still need to know it would be quick excluding that. |
Right, cgimap indeed depends on UTC-0, and there will be some screw up otherwise: zerebubuth/openstreetmap-cgimap#150. |
Quick question on this one. Cgimap has a few queries like the example below. Assuming we're running the server in a non-UTC-0 time zone, would we need to apply to changes to the code then once the data type has an additional m_connection.prepare("extract_nodes",
"SELECT n.id, n.latitude, n.longitude, n.visible, "
"to_char(n.timestamp,'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') AS timestamp, "
"n.changeset_id, n.version, array_agg(t.k) as tag_k, array_agg(t.v) as tag_v "
"FROM current_nodes n "
"LEFT JOIN current_node_tags t ON n.id=t.node_id "
"WHERE n.id = ANY($1) "
"GROUP BY n.id ORDER BY n.id") |
I think that entirely depends what timezone you want cgimap to return those timestamps in... If you want them in UTC then you're good, if you want them in local time then no. |
The "Z" (zulu) character in the timestamp string implies that all timestamps should be returned in UTC-0, no matter what time zone the server runs in. I also remember seeing some issues when populating the database via osmosis from a UTC+3 terminal (see this year's GSoC project: osmlab/yalcha#11). This ended up creating wrong timestamps all over the place. Not sure, if this behaviour would be any different with osmosis and |
Yes, that code looks to me like it needs no changes. It's already forcing the timestamp to be considered as UTC by ending in Z with no offset shown. That will work currently on databases configured as UTC, and after the column change, on all servers regardless of their timezone. (In fact, changing the will fix a potential source of bugs that developers might encounter. If today at noon in California you insert a node into your local development database using raw sql (INSERT INTO current_nodes VALUES .... ( ... NOW()... ) for some development work stuff, then the timestamp would contain "2018-11-21T12:00" (i.e. local time) and cgimap would show that as 2018-11-21T12:00Z" (UTC) which is a different time entirely. But when we change the column type, the NOW() would instead store the UTC timestamp in the timestamp field, and the potential bug disappears, with no changes needed to cgimap.) |
That's not quite what it's doing. It's taking the timestamp from the database, formatting it appropriately and then sticking an actual letter Z at the end of the string (like the T, it's double quoted - it's not a formatting character like YYYY so postgres is just adding that character to the string). So there's no timezone conversion happening in the to_char method. It's taking the
Yes, this is the same problem. Osmosis will have read the UTC timestamp from the XML file, handled it internally as UTC all through the pipelines, and then when writing it to the database the pg connection library will have converted that time to local time when storing it as |
Right, that's what I had in mind, but somehow failed to make it clear. it's a mere string indicator to the data consumer, that the timestamp should be considered to be UTC. To postgres, it's just some opaque string which happens to be returned as part of the response. |
I found this in the postgresql 9.2 release notes, which sounds hopeful that the table changes might be done without extended downtimes:
I think we'll still need a short downtime to get the locks, but hopefully will be fast apart from that. Is there a replica that this could be tested on? Or should we just start with the smaller tables and work our way up? 😃 |
A few words on the status quo: by convention, the column type conversionI did some experiments using the following statement, which aims at preserving the original UTC based timezone value.
NB: leaving out This statement took around 1-2 seconds on a 300k table. It's not entirely clear to me what the performance impact will be on a UTC+0 system when processing a few billion rows. With this change in place, the timestamp is now shown as follows:
Rails portapi/0.6/node handled by Rails port returns: -> Rails port looks ok! cgimapContrary to some earlier statements, cgimap shows incorrect timestamps now, in case the server isn't running on UTC+0. http://localhost:31337/api/0.6/node/5002730175
I think we would have to add
osmosis
Confirmed. Osmosis also breaks by this change and produces osmChange files with incorrect timestamps: |
The values need to be converted from one type to another. It's a trivial conversion, but it still needs to be done. Because the It's probably possible to do this without an outage but not easy. |
@pnorman, @gravitystorm : what would be your take on how to address the breakage of cgimap, osmosis, and other tools due to this change on a non-UTC system? |
@pnorman really, even if the system is running on UTC so that nothing changes? If the columns need to be rewritten then I think this will pretty much be a non-starter as we would need a downtime measured in hours if not days. |
My take is that you're already broken if you're running those on a non-UTC system. The best transition plan is probably to force the timezone for the users those clients connect from, or to set appropriate libpq env vars to force the timezone. |
This is where I'd defer to a consultant as it's a reasonably well defined problem, and sufficiently advanced that it's outside my experience. |
I see this more as a hack than a real solution. in the long run, we would still require various code changes across different applications to make it work as seamless as today. I realize that lots of people recommend switching to "with timezone". I'm not totally opposed to the change as such, but would prefer to see some well thought out strategy for its implementation. |
There are a few pointers in the documentation and source code: According to: https://doxygen.postgresql.org/tablecmds_8c_source.html#l09176 (points to relevant Postgres source code):
According to https://www.postgresql.org/docs/9.5/sql-altertable.html Adding a column with a DEFAULT clause or changing the type of an existing column will require the entire table and its indexes to be rewritten. As an exception when changing the type of an existing column, if the USING clause does not change the column contents and the old type is either binary coercible to the new type or an unconstrained domain over the new type, a table rewrite is not needed; but any indexes on the affected columns must still be rebuilt. My conclusion would be (TBC):
NB: I'm assuming that the timestamp to timestamptz conversion routing needs to be called for billions of rows, only to find out that there's no change requiring a rewrite in case of UTC. I haven't found any description of an optimization, that would simply change the table metadata. Why is the timestamp conversion not binary-coercible?
According to https://www.postgresql.org/docs/9.5/catalog-pg-cast.html Field castmethod: Indicates how the cast is performed.
|
This change is to avoid the hack of forcing every client to specify the timezone. Right now you have to do that, in the future you won't if we make this change. |
It would be helpful to exactly name those issues. FWIW, I’m running rails, cgimap and osmosis diff replication on utc+1 & utc+2 and don’t have to mess around with time zones at all, it works out of the box. I can only recommend to try this out for yourself. Looking the rails port, you will notice that time is always treated as UTC (e.g. For the changeset upload i added in cgimap, I used |
Not quite - it appears to work out of the box. This is because the clients you list all assume that the data stored in the 'timestamp' columns is UTC, when it is actually 'local time'. Since the assumption is symmetric (i.e. the same mistake on writing and reading), you don't see the problem. That, however, is not sufficient to state that the problem doesn't exist. :-)
Sure, we need to fix any tools that already make broken assumptions. I'm not going to suggest we make changes that break anything, this whole topic is about fixing things properly. But this change will make the timestamp storage correct, and allow people to make future clients (or even trivial things, like calling now() from an SQL statement) work reliably without having to deal with this headache over and over for ever more.
We can't just say that we'll never change anything in our tables ever again! If this does involve large table changes, then we can work out what the zero-downtime migration path would be, and do that. We could practise multi-stage migrations on the smaller tables where there is less impact. |
OK, I mentioned to @tomhughes last week that I feared this might be the case and that it needs more investigation. From my initial investigations it appears that there isn't a version of
If there's a way to avoid the synchronous changes, that would be worth looking into. For example, cgimap could try column type detection, or we can find a different time formatting function, or so on. Basically we should try to come up with a solution that allows cgimap to work with both timestamp and timestampz columns interchangeably. |
Yes, that's pretty much in line with http://phili.pe/posts/timestamps-and-time-zones-in-postgresql/ The answer is semantics. Whenever you encounter a timestamptz, you know it denotes absolute time. It was stored in UTC. However, when you come across timestamp, you can’t possibly know in what time zone the timestamp is just by looking at it. It is merely a wall time. Maybe it’s UTC, or maybe the developer stored it in local time. Without additional context, you can’t be sure. What we rely on here is the additional context.
Right, maybe we could set the timezone from within cgimap et al. via I'm not exactly sure if this is what @pnorman proposed earlier, it sounded more like he wanted to control this from the outside via some environment parameter, which I don't find an ideal solution. |
Currently the current_nodes, current_ways and current_relations tables include a timestamp column of type
TIMESTAMP WITHOUT TIME ZONE
.I've asked around and consulted some sources and the advice I got was best summarized as
19:12 <RhodiumToad> pnorman: unless you're writing a calendaring/scheduling app, timestamp with time zone is pretty much always the way to go
This is not purely a theoretical concern. Currently there is no way to my knowledge to load to an apidb that does not result in time zone errors. See this osmosis discussion
Neither the rails port nor cgimap returns the correct timestamps right now on any instance other than osm.org running on a server where the local time is not set to UTC, because the problem is in the database.
Cross-reference pnorman/openstreetmap-api-testsuite#1
cc: @brettch, @zerebubuth, @iandees
The text was updated successfully, but these errors were encountered: