MongoDB logrotate script

MongoDB has log rotation functionality built in, but since I run CentOS I like to have everything managed through logrotate. The 10gen RPM I installed doesn’t have a logrotate script, so I wrote one. Create file /etc/logrotate.d/mongod:

/var/log/mongo/mongod.log {
        daily
        rotate 30
        compress
        dateext

    missingok
    notifempty
    sharedscripts
    postrotate
        /bin/kill -SIGUSR1 `cat /var/lib/mongo/mongod.lock 2> /dev/null` 2> /dev/null || true
    endscript
}

logrotate runs at 4 AM daily by default (via the script in /etc/cron.daily/logrotate). The file above rotates the mongod.log file daily, retaining 30 days of files, appending the date to each one after rotation (rather than the “.1” or “.2” suffix) and then gzipping it.

Teaching myself node.js: Part 3

I’ve been playing around with node, JavaScript & MongoDB a bit with this project and one of the things I ran into was MongoDB’s lack of a sequential ID. MongoDB defaults to ObjectIDs for primary keys in collections, and they have good reasons for doing so, but in writing this app, I’d rather have URLs look like “/question/423” than “/question/3001024e521e9c6500000000”.

Fortunately, there’s a relatively convenient workaround for this problem, detailed here. Essentially, create a collection in which each document is a counter. The ID for the document is its “name” and the property “next” contains the next value. This is similar to creating and using a sequence in PostgreSQL, though hopefully in the future they’ll provide an easier way to do this – perhaps a built-in sequence object.

First, in the mongo shell, I created the counter(name) function as specified in the Mongo wiki:

> counter
function counter(name) {
    var ret = db.counters.findAndModify({query:{_id:name}, update:{$inc:{next:1}}, 'new':true, upsert:true});
    return ret.next;
}

Then I created the counter document for the “questions” collection (so questions can have numeric IDs):

db.counters.insert({_id:"questions", next: 1});

This is optional – the counter() function will create the document in the counters collection if needed, with an initial value of 1.

That’s it. Each call to counter() will now return an incrementing number:

> db.counters.find();
{ "_id" : "questions", "next" : 3 }
> counter("joe");
1
> db.counters.find();
{ "_id" : "questions", "next" : 3 }
{ "_id" : "joe", "next" : 1 }
> counter("joe");
2
> counter("joe");
3

Cool. So how do we save the counter() function in the DB so we can use it in queries without having to define it every time? Fortunately Mongo makes saving functions on the server easy:

> db.system.js.save( { _id : "counter" , value : counter });
> db.system.js.find();
{ "_id" : "counter", "value" : function cf__2__f_counter(name) {
    var ret = db.counters.findAndModify({query:{_id:name}, update:{$inc:{next:1}}, 'new':true, upsert:true});
    return ret.next;
} }

Calling the stored procedure directly in the shell is sort of strange, but it works:

> db.eval("return counter('question')");
3
> db.eval("return counter('question')");
4
> bye
[Wed Jun 22 13:14:10 evan@EvanMBP 3 ~]$ ~/Downloads/mongodb-osx-x86_64-1.8.1/bin/mongo questionsMongoDB shell version: 1.8.1
connecting to: questions
> db.eval("return counter('question')");
5

Unfortunately, I was unable to figure out how to call the stored function from within the mongoskin driver, and ended up writing analogous code, calling findAndModify() within the app code. I finally got it:

        db.collection('counters').findAndModify(
                {_id:'questions'},
                [],
                {$inc : {next: 1}},
                true,
                true,
                function(err, counter) {
                        if (err) { throw new Error(err); }
                        var ins = { date: new Date(),
                                author: req.body.author, body: req.body.body,
                                tags: tags, tag_count: tagCount,
                                answers: [], votes: 0,
                                _id: counter.next
                        };
                        db.collection('questions').insert(ins, {});
                        res.end('Added new question: '+req.body.body);
                }
        );

Question detail
Question detail

I tried throwing some code in there to query by either ObjectId() or integer but it didn’t really work (Edit: I tried this again on Linux and it worked fine), so I just deleted all the documents with ObjectIds and everything seems to work fine now. I think the next step is going to have to be some sort of authentication/session stuff, because typing your name in every time kind of sucks (and makes it hard to do things like list questions by user).

Full code for this revision is here.

Teaching myself node.js: Part 2

I did a little more work today on my little node.js project and added the ability to click on a tag and have the app list all questions with that tag, and a super basic method for adding new questions. Neither of these were really complicated; the most “challenging” part was figuring out how to create links and form inputs with jade. In the end I was surprised at how easy it really was.

For the tag search I just copied the app.get('/questions') route to a new app.get('/tags/:tag') route in app.js and modified the mongo find() query to search for the tag. I created a new Jade template for this, but I suppose the same list.jade template would have worked, since the only thing that was changed was the contents of the questions array.

For adding a new question I created an app.get('/question/new') route that loaded a jade template with the form (addquestion.jade), and which did a POST back to /question and the corresponding app.post('/question'). Right now the form just takes text input fields, but eventually there’ll need to be some notion of users, and other fun functionality. Also after doing the insert into Mongo, it just spits back some plaintext response, but it gets the job done.

I should add that this is my first time writing a webapp using “routes.” It’s been a while since I really did any front-end coding at all and I’m used to the “old school” method of one script per function, where the “add a new question” function would be handled by “addQuestion.php” for instance. Using routes and MVC are part of what I’m trying to learn in doing this.

Commit for this version is here

Teaching myself node.js: Part 1

I’ve been meaning for a while to learn node.js. I read a couple of books and plenty of blog posts but in the end I find, as usual, I can only learn by doing. I have most of the fundamentals to get started, I just need to have a project. The typical tutorials I’ve found focus on creating a blog; I’m thinking of something more along the lines of a Quora question/answer site. I’ll be trying to write this as I go, which will hopefully prod me into actually doing it this time. (I have a tendency to bounce around different tenses and I expect this will be worse than usual – stuff will likely be written in past/present/future at various points).
Continue reading “Teaching myself node.js: Part 1”