Täytyypä mainostaa hiukan, sillä ensimmäinen toteuttamani kaupallinen iPhone-sovellus Älypää-peli on nykyään myynnissä iTunes-storessa.
Pelin ensimmäinen versio on tarkoituksella hyvin simppeli ja edullinen (0,79e) eikä siinä ole kovin laajaa kysymyspatteria mukana. Palautteen perusteella kysymysten määrä onkin se mitä pelaajat haluaisivat lisää.
Kannattaa kokeilla jos on käytössä iPhone tai iPod touch ;-) Pelin jukaisija on työnantajani Sanoma Entertainment.
By default, Django 1.0 supports only a single, hardcoded database connection, defined by the settings.DATABASE_XXX variables. All models use the same connection.
A scalable website needs several different database connections when MySQL is used. Models might be stored in separate databases, and sometimes a read-only replica of a database might be used for querying. For sharding purposes, several different databases might be used for the same model.
The "objects" attribute of Django models can be replaced with a custom Manager class. This allows you to override the get_query_set() method, which chooses the database connection. For example:
from django import db import new class CustomSettings: DATABASE_HOST = '...' CUSTOM_SETTINGS = CustomSettings() class CustomManager(db.models.Manager): def custom_conn(self): conn = db.backend.DatabaseWrapper() cursor = lambda mgr: mgr._cursor(CUSTOM_SETTINGS) conn.cursor = new.instancemethod(cursor, conn, db.backend.DatabaseWrapper) def get_query_set(self): query = db.models.sql.Query(self.custom_conn()) return CustomQuerySet(self.model, query) class MyModel(db.models.Model): objects = CustomManager()
Unfortunately, Django still uses a hard-coded connection object to commit transactions after saving models. This must be overridden or nothing will happen. First the INSERT:
class CustomManager(...): ... def _insert(self, values, **kwargs): return self.insert_query(self.model, values, **kwargs) def insert_query(self, model, values, return_id=False, raw_values=False): conn = self.custom_conn() query = db.sql.InsertQuery(model, conn) query.insert_values(values, raw_values) rv = query.execute_sql(return_id) # Need to commit here conn._commit(); return rv
The UPDATE can't be committed in CustomManager, because it's called directly in the QuerySet object. This means you have to use an custom QuerySet object like this:
class CustomQuerySet(db.models.query.QuerySet): def _update(self, values, **kw): rv = super(CustomQuerySet, self)._update(values, **kw) # Need to commit here self.query.connection._commit() return rv
When sharding is used, the database connection depends not only on the model, but also some parameter that needs to be passed with the query. Choosing a readonly connection is very similar. This can be done by adding a method to the custom Manager class:
class CustomManager(...): ... def get_query_set(self, shard_id=None, readonly=False): # Use the parameters to choose the connection .... def shard(self, shard_id): return self.get_query_set(shard_id) def readonly(self): return self.get_query_set(None, True) # To query the model from a specific shard MyModel.objects.shard(42).get(id=76) # To use a read-only connection MyModel.objects.readonly().get(id=76)
Multiple database and sharding are possible, but they require overriding some Django internals. This might break in future versions of Django.
Apparently work is already in progress for Django support multiple databases by default. Hopefully all the cases described in this article will be supported (multiple databases, read-only databases and sharded databases).
The DELETE operation was actually quite difficult to implement, because Django uses a global module-level delete_objects() function instead of going through the Manager and/or QuerySet. My current solution is to override the Model's delete() and manually delete by the primary key from the table.
ForeignKey references in models also cause problems when the field is not really a foreign key but resides in another database. I had to change them into IntegerFields and handle them manually in application code.
It would be really nice if Django had some generic database query context, which could be set up when querying or inserting/updating/deleting models. This context could be used to carry information about what kind of database connection is needed. The global module-level connection object could then be replaced with a call to some overrideable function that would see the context and could decide which connection to return.
A typical requirement of any website is to enforce all registered usernames (and sometimes also email addresses) to be unique. This is trivial to do with a relational database like MySQL, but CouchDB doesn't have unique keys.
The usual way is to make the username (as chosen by the user) the _id of the User document. That will enforce uniqueness, but it also makes it difficult to change the username later, when there are lots of references to the document using the _id. It also doesn't solve the problem of having additional uniqueness requirements for other fields like email addresses.
Instead of the usual way, I'm using a separate document to hold the unique username as its _id, and another (auto-id) document that contains the actual user data. Each user would then be described in the CouchDB database by two documents like this:
{ "_id": "username/kennu", "type": "UsernameReservation", "user_ref": "71bacba712381adefg13712312312369" } { "_id": "71bacba712381adefg13712312312369", "type": "User", "username": "kennu", "password": "xxxx", "email": "webmaster@kfalck.net" }
This makes it easy to refer to the User document using the permanent auto-generated _id, while uniqueness is enforced by the separate UsernameReservation document. When the username is changed, a new UsernameReservation document is created to reserve the new name, and then the old document is deleted.
Additional unique fields can be added fairly trivially with documents like this:
{ "_id": "email/webmaster@kfalck.net", "user_ref": "71bacba712381adefg13712312312369" }
Because CouchDB doesn't have transactions, it's complicated to ensure that the username reservation happens only when the User document is also saved successfully with the corresponding username. In my code (I'm using couchdb-python and Document schema classes), I've defined a "save" method that will execute the following steps:
I'm pretty sure that can be made more beautiful. The basic problem is how to implement a multi-step CouchDB transaction properly in client-side code. It's impossible to make it survive server crashes and similar situations, but maybe a generic transaction could remember the _revs of the documents that were modified, and then restore them to the original state if something goes wrong.
There is one final challenge related to distributed CouchDB: what happens, if the same _id (e.g. username/kennu) is created simultaneously on two or more database servers?
In this situation, CouchDB will mark both documents as _conflicting revisions and more or less randomly choose one of them as the winner. It's up to the application to decide what to do when it encounters a username in _conflicting state. There are a few alternatives that are a bit nasty:
Since usernames and emails are typically not changed very often, it might be more feasible to use only one master database for them. In this scenario, the application would write UsernameReservation documents only to the master, which guarantees they are never conflicting with each other.
Creating unique fields in CouchDB seems to be quite challenging compared to relational databases. It would be very helpful if CouchDB supported unique fields directly, or if it provided some kind of multi-step transactions for implementing them reliably using separate document _ids as described in this article.
Jakob Nielsen heittää uusimmassa Alertboxissaan ilmoille melkoisen trollin. Nielsenin mukaan webbisaittien pitäisi alkaa näyttää käyttäjien kirjoittamat salasanat selkokielisinä ruudulla, ja korkeintaan tarjota erillinen checkbox, jolla ne saa halutessaan piiloon.
Minusta tämä on yksinkertaisesti typerää sekä tietoturvan että käytettävyyden kannalta:
Siinä saattaisi kuitenkin olla järkeä, että salasana on oletuksena piilotettu, ja sen saa halutessaan näkyviin erillisellä checkboxilla. Olen kuitenkin sitä mieltä, että tämä on parempi jättää selaimen ominaisuudeksi, ja käyttää edelleen input type="password" -tyyppisiä lomakekenttiä aivan kuten ennenkin.
Installed the Drupal Twitter module and testing if new blog post titles appear on my Twitter account.
Karin blogin kommenttien inspiroimana koodasin tähän blogiini simppelin "Like"-nappulan. Sen pitäisi toimia Facebookin tyyliin ajax-periaatteella. Kyseistä nappia voi läppäistä, jos kirjoitus viehätti, muttei huvita kokonaista kommenttia kuitenkaan kirjoittaa.
Olen itse huomannut, että Facebookin "Like"-toiminto on paljon käyttökelpoisempi kuin erilaiset 5-tähden scoret tai "thumbs-up/down"-tyyppiset arvostelut. Ne vaativat arvostelijalta tietoista ajatustyötä, mutta Like-nappia voi painaa aika huoletta, jos jutussa vain on jotain hauskaa tai kiinnostavaa.
PS. Hakkereille tiedoksi, että tätä Like-nappia on helppo huijata esim. keksit tyhjentämällä, mutta sillä onnistuu lähinnä muuttamaan yhtä counteria tietokannassa...
Kun ensimmäistä kertaa luin ZFS:stä jokunen vuosi sitten, olin innoissani. Vihdoinkin täysin virtuaalinen tiedostojärjestelmä, joka mahdollistaisi fyysisten levyjen lisäämisen ja poistamisen lennossa. Käyttäjä vain näkisi käytettävissä olevan kokonaislevytilan kasvavan tai pienenevän vastaavasti.
Normaalistihan uuden fyysisen levyn lisääminen virtuaaliseen levyjärjestelmään edellyttää RAID-konfiguraation päivittämistä ja koko tiedostojärjestelmän luomista uudelleen. Se on ainoa keino saada usea fyysinen levy näkymään yhtenä virtuaalisena kokonaisuutena.
Ei siis ihme, että Apple kiinnostui ZFS:stä. Sen idea sopisi mainiosti Mac OS X:ään, joka on muutenkin suunniteltu yksinkertaiseksi ja helppokäyttöiseksi mutta teknisesti edistyneeksi käyttöjärjestelmäksi.
Mutta nyt näyttää siltä, että ZFS ei ole lunastanut lupauksiaan. Solaris-käyttäjien kokemusten perusteella se on edelleen epäkypsä, sekoaa herkästi tietyissä tilanteissa ja aiheuttaa datan menetystä. Apple puolestaan on poistanut saitiltaan kaikki viittaukset ZFS:ään, vaikka siitä piti tulla osa Mac OS X:ää.