Thursday, September 20, 2012

MySQLdb book and other news

Did you know there is a book specifically addressing how to use MySQLdb? MySQL for Python was published by Packt Publishing in September 2010. If you're worried about two years being an eternity on the internet, don't: There hasn't been a new release of MySQLdb since the book was published.

Speaking of new releases, I built a Windows installer of MySQLdb-1.2.3 for Python-2.7 and using the MySQL Connector/C 6.0. This should be able to connect to any modern version of MySQL (4.0 through 5.5 and newer), and as I understand it, it should also work with MariaDB, but this is not yet tested.

Additionally, there is a 1.2.4 release coming out in the near future which will be a bug-fix release only. It will support Python-2.4 through 2.7 (and should be compatible with Python-2.8 when it arrives). There is some work towards Python-3 compatibility (specifically, 3.2.3), but unfortunately, I can't easily support Python < 2.7 and Python >= 3 at the same time. So after 1.2.4 is out, there will be a 1.3.0 release shortly thereafter which will be for Python-2.7 and 3.2 or newer (and probably earlier).

Longer term: I've also been working on a MySQLdb-2.0 version which is almost completely rewritten from scratch on the Python side, and modularized (and some code removed) on the C side. It's going to have pluggable drivers, with the intention of having the option of using libmysqlclient (the standard C library), libmysqld (the embedded server library), some kind of pure Python driver, and eventually a driver for Drizzle. MySQLdb-2.0 is actually going to be renamed Moist, and no, I don't yet have the code in github yet, but it will be what is the MySQLdb2 repository on SourceForge.

Is the project leaving SourceForge? It remains to be seen. Every time I get fed up and I'm sure I'm about to move, they have some big update which makes things better. The current location is pretty well-known, and the project has been there since 2001. I do think I will be mirroring a git repository between SF and github at the very least; all the cool kids are on github these days.

Monday, February 22, 2010

Asynchronous programming and MySQLdb

It was asked in the previous post whether or not there would be better async support in MySQLdb-2.0. The answer is a qualified yes.

libmysqlclient (the MySQL C client library) has blocking calls, and doesn't have a true async interface. In a nutshell, the three blocking calls that are most important are mysql_query(), mysql_store_result() or mysql_use_result(), and mysql_fetch_row() (sometimes).

The original design of MySQLdb uses mysql_store_result(), which stores the entire result set in a MYSQL_RESULT structure; in this case, mysql_fetch_row() does not block. To save memory, the result is immediately converted to Python types and then freed.

An alternative cursor implementation (SSCursor) uses mysql_use_result(), which does not internally buffer the result set; this does cause mysql_fetch_row() to block, however. A further complication is that no new queries can be issued on the connection until the entire result set has been fetched. This is the primary reason why mysql_store_result() is used by default, because overall it causes less problems.

The main issue with using mysql_store_result() it can consume a lot of memory the result set is large. There's still the option of using a LIMIT clause, but it's inconvenient. But mysql_use_result() has it's own problems, in that you have to be careful to cycle through the entire result set before issuing another query. Otherwise you will get the "commands out of sequence" error.

I am pretty sure that inside the C client library, the wire protocol is exactly the same, and mysql_store_result() is just pre-buffering everything.

So in the end, anything using the C API is going to have to deal with some blocking calls. However, there are some design changes that will make some of these limitations a bit easier to deal with.

First, all the various Cursor classes are going away, and there will be only one True Cursor.

By default, cursors will still use mysql_store_result() on most queries, because there is a lot that it works very well for. This includes INSERT, UPDATE, and DELETE, which do not return any rows, but also some meta-queries such as SHOW WARNINGS, SHOW TABLES, etc. which do return rows but always a relatively small number, and don't take a long time to execute.

SELECT statements, on the other hand, will be detected, and those queries will use mysql_use_result() instead so that rows are not buffered in the C client. They will only be fetched upon demand.

The irony of using mysql_use_result() on SELECTs is, you can't scroll (mysql_data_seek()) on the result set, and this is the primary use case for scrolling. However, I still expect to make scroll work, possibly in a limited way, because these cursors will still be somewhat buffered. There will be a user-configurable maximum row limit that will be buffered, and once that buffer is filled, the oldest rows will be discarded. This limit will probably be 1 row by default.

How will the "commands out of sequence" error be avoided? If another cursor is created, the first cursor will be flushed, i.e. any remaining rows in the result set, and any additional pending result sets, will be read so that the query can be issued. There is also a clear method for the cursor, which also reads all the rows, but instead of buffering them, it discards them. It also avoids doing any Python type conversion.

So if you've gotten this far, you're probably wondering: How does any of this affect asynchronous use? Well... it doesn't. The C client library just isn't designed for that sort of thing. But there is still hope.

I've wanted a ctypes implementation that would still use the libmysqlclient library but wouldn't require compiler tools to build (and hopefully shut up Windows users). At the time, ctypes was still very early, and had not made it's way into the Python standard library yet (included as of 2.5). Fortunately, Jason Coombs has a patch against MySQLdb (jaraco.mysql) which implements this. The reason I haven't integrate this patch it would replace the original driver, instead of being an option. MySQLdb was never designed for multiple drivers. This has made it pretty close to the top of my TODO list for MySQLdb, because there's another case where this would be useful: There currently is no way to have _mysql (the current C driver module) built against libmysqlclient and libmysqld (the embedded server library, which uses the same API) at the same time without using virtualenv or something like that.

OK, so now the async people are asking: So how does this help async programming again? Well... it still doesn't. To truly have a non-blocking async driver probably means there will need to be a new implementation that is designed with that in mind. A pure Python implementation could do this. I have a start on one which I got from Monty Taylor of MySQL some time ago. It's not asynchronous either, but could be made so. At least it looks like a good starting point.

But here's the thing: As far as I know, there is no standard database API for Python which supports asynchronous operation. And it seems like there should be one. Maybe it's time for PEP-249 to be extended for an asynchronous API. Otherwise every database implementor is going to end up doing their own thing, and it sounds like there is a need for this sort of thing.

New releases coming soon

Kyle Vanderbeek is going to take over as release manager for MySQLdb-1.2. We should have one more release candidate of 1.2.3 first, followed quickly by the final release.

Development on MySQLdb-2.0 has been progressing, and has recently moved to a Mercurial repository on SourceForge. This was imported from the SVN trunk. If you pull from the SVN trunk in the future, you may be disappointed.

2.0 is turning into a very major rewrite. I should have an alpha release soon. For now, the hg repository builds and passes all tests, but there are probably a few things that aren't thoroughly tested yet, particularly scrolling on cursors. I'll post more detail along with the alpha release.

Python3 support is not immediately in the works, and I probably won't work on it until I am close to a beta. At this point, I would target Python-3.1, maybe 3.2. 3.0 would probably work too.

Thursday, March 19, 2009

MySQL-python-1.2.3 beta 2 released

I released the second beta of MySQLdb-1.2.3 over the weekend. So far I've gotten a fair number of downloads but not a lot of feedback. I did find out though what small tweaking is required to build on Windows. It's also in the Python Package Index, so if you can also install using easy_install MySQL-python. Once I make the final release of 1.2.3, I'll put up more eggs for fringe operating systems (Mac OS X, Windows).

Saturday, February 21, 2009

Sprinting at PyCon 2009

I've got a sprint scheduled now for PyCon 2009. I can only be there for the first day of sprints. If there's enough interest, we can probably find a way to sprint earlier during some of the open space session; or it can continue after I'm gone.

PyCon 2010 will be in Atlanta, GA, which is a lot closer to home, but not close enough that I can avoid lodging expenses.

Sunday, February 1, 2009

Project Status: Community Participation Needed

John Eikenberry has taken over work on ZMySQLDA for some time now and has released ZMySQLDA 3.1. I don't have any ongoing Zope deployment except some Zenoss, and use MySQLdb directly, so I'm not a good test candidate. Superficially, there don't seem to be any outstanding issues.

Since Python 2.6 and 3.0 were released, there has been a lot of demand for prepackaged MySQLdb for those versions. MySQLdb-1.2.2 seems to throw some warnings on Python 2.6, due to a new Python set type (and the old Set module being deprecated), and some old-style exception usage. These problems should be fixed in the SVN version (MySQLdb-1.2 branch). MySQLdb was originally developed for Python 1.5 so some old crufty stuff is still hiding out in there. There are also some build fixes for Mac and Windows.

MySQL-1.2.3 is not too far off. There are probably a couple more fixes that need to go into the 1.2 branch. This will almost certainly be the last release in the 1.2 series. One outstanding question is what versions of Python should be supported in 1.2.3? I've settled on no longer supporting Python 2.3, but I won't go out of my way to break it yet (i.e. decorators, generator expressions). Python 2.4 seems reasonable to support. My current development platform (Ubuntu) has Python 2.5.2 so that's what I'm going to test against. I also plan to test against Python 2.6.

Python 3.0 is a different beast. I'm not sure if it will be possible to have a single version of MySQLdb that works on both Python 2.x and 3.x yet. I'm assuming not. I don't think I'll have Python 3.x support until Python 3.1 at least, though that seems to be not too far off. Guido van Rossum has indicated that Python 3.x will have a faster release cycle than Python 2.x; see PEP-3000. We might even see some early 3.1 releases by PyCon 2009.

MySQLdb-1.3, the precursor to 2.0 and the SVN trunk, is on hold for right now. Some of the fixes that are in the 1.2 branch need to be backported. Currently it does not do any type conversion; this has been removed from the C module and will be done in Python. I don't expect this to adversely affect performance.  A lot more will be done using generators and more modern Python-isms. The question here is do I support Python 2.x or only Python 3.x. I'm inclined to say I'll develop it now for Python 3.x, and save backporting for later.

MySQL-5.1 was finally released. There are no big C API changes that I've seen (maybe no small ones either), so you should be able to build against it. For that matter, if you have MySQLdb built against the 5.0 client, I expect it to work fine with a 5.1 server, so save yourself some trouble and don't upgrade if you don't have to.

Similarly, if your OS vendor supplies packages for MySQLdb, use them. MySQLdb is known to be in a lot of Linux distributions, including Fedora, RHEL, Debian, Ubuntu, Gentoo, and others. It's also in the various *BSD distributions.

If you are running Windows or Mac OS X, I still don't use those platforms so I won't be building my own packages for them. If you want to contribute your own packages, I'll host them on SourceForge. Make sure you specify what version of Python and what version of MySQL it is built against.

I am planning a short sprint at PyCon 2009; it will not go any longer than Tuesday, unless there is a lot of unanticipated demand. In light of this, this is the approximate release schedule:

Late February 2009: MySQLdb-1.2.3 beta 1
Late March 2009: Sprint
Early April 2009: MySQLdb-1.2.3 release candidate 1
Mid-April 2009: MySQLdb-1.2.3 final

In order for a sprint to be effective, it can't be just me. I need people to help find, report, test, and fix bugs. I don't think we need more than two days to do this. I suspect we can get a lot done before the official sprint starts. We can probably make the trunk usable. If I could get one or two committed people at the sprint (not counting myself), I'd be happy.

Lastly, MySQLdb development is entirely funded by donations. Nobody pays me to work on it. I've been working on it for the past 10 years, 8 years on SourceForge.  I have to plan development around my work schedule to avoid any conflict of interest or intellectual property issues. For the same reason, I am attending PyCon out of my own pocket. Donations are appreciated, though I could also use some interested developers. One or two have come forward lately; the important thing is that you submit patches through the bug/patch tracker. If your patches are good quality, I'll probably give you write access to SVN.

Feedback is solicited and welcomed.


Wednesday, May 14, 2008

Zenoss Deathmatch

I've been working for the last week on implementing Zenoss to replace Nagios and Cacti. Individually Nagios and Cacti are pretty good at what they do, but they don't integrate well.

Nagios is primarily an availability monitor, so it's good for notifying you when something goes down, or a disk is filling up, or the load average is too high. etc., but it's not so great for monitoring performance. Nagios 1.4 uses text configuration files. There is a templating system which can be helpful if you have a lot of identical systems.

Cacti, on the other hand, is pretty good at monitoring performance, as in how much bandwidth are you using, resource utilization, and so on with nice long-term graphs using RRDtool, but it's not so great for notifying if something is down. Cacti is almost exclusively SNMP-based, and as a result, you can usually just point it at a device through the web interface and it will auto-discover everything interesting. If you have more than a few hundred items to measure, you need to use cactid, which is a very fast threaded poller written in C.

I've been using both for about 3-4 years separately, but because they don't integrate easily (even though both use MySQL as their backend storage), there's a lot of duplication of effort in getting both of them configured.

And then there's Zenoss. Zenoss does both availablity and performance monitoring, with long-term graphing using RRDtool, log analysis, and network-based auto-discovery. Zenoss is written in Python using the Zope-2 framework. Most of the device metadata is stored on ZODB, Zope's native object database. Long-term performance data is stored in RRDtool. Event logs are stored in MySQL.

Everything in Zenoss integrates together very well. The data is faceted in the sense that you can browse devices by location, by class, by group, or by system. It has a built-in syslog server, it can use WMI for monitoring Windows systems, it has very flexible event handling.

There are still some rough edges in 2.1.92, which is a beta for 2.2. First is, it's a bit of a memory hog and I'm inclined to believe there are some memory leaks. After a day or two the main process will start to use over 200 MB; restarting tends to knock it back down to to around 100 MB or so.

Syslog support has some issues. When I first started feeding it some syslog data, all the events were being classified as "/Unknown". This is normal. Once you have some log entires, you can then tell it to map that entry to an event. The problem was, the events had components (the process name when parsing syslog data), but they had no event classes set. Looking at the code, it seemed like it should have been setting the event class ID to whatever the component/process name was. It just wasn't. After some Googling, I found out the code to build the event class key was just plain broken. After making these suggested changes, I could start mapping events.

Another syslog problem was in parsing the hostname. I have a satellite syslog-ng server in a remote location that logs to my central syslog-ng server. Because of this, the hostname has the relay information in it. Zenoss' syslog support has an option to parse this though, so no problem, right? Despite turning this on, I was still getting entires like IP/IP, so back into the syslog code. It turns out, Zenoss expects the separator between the two hostnames to be "@", and syslog-ng uses "/'. Easy fix in the code, but I suspect this may work for the standard syslog, and it needs to be a configuration option.

Despite all of this, I like Zenoss a lot. I am running it parallel with Nagios until I get all the event handling nailed down. I might need 2 GB RAM on the monitoring server though, and I have already moved the MySQL database onto a different server.