9dc8c077486f8907374eaab1e1bb449143904d14
[blerg.git] / www / doc / index.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <title>Blërg Documentation</title>
5 <link rel="stylesheet" href="/css/doc.css">
6 </head>
7 <body>
8
9 <h1>Blërg</h1>
10
11 Blërg is a minimalistic tagged text document database engine that also
12 pretends to be a <a href="/">microblogging system</a>.  It is designed
13 to efficiently store small (&lt; 64K) pieces of text in a way that they
14 can be quickly retrieved by record number or by querying for tags
15 embedded in the text.  Its native interface is HTTP &mdash; Blërg comes
16 as either a standalone HTTP server, or a CGI.  Blërg is written in pure
17 C.
18
19 <ul class="toc">
20   <li><a href="#installing">Installing</a>
21     <ul>
22       <li><a href="#getting_the_source">Getting the source</a></li>
23       <li><a href="#requirements">Requirements</a></li>
24       <li><a href="#configuring">Configuring</a></li>
25       <li><a href="#building">Building</a></li>
26       <li><a href="#installing">Installing</a></li>
27     </ul>
28   </li>
29   <li><a href="#api">API</a>
30     <ul>
31       <li><a href="#api_definitions">API Definitions</a></li>
32       <li><a href="#api_create">/create - create a new user</a></li>
33       <li><a href="#api_login">/login - log in</a></li>
34       <li><a href="#api_logout">/logout - log out</a></li>
35       <li><a href="#api_put">/put - add a new record</a></li>
36       <li><a href="#api_get">/get/(user), /get/(user)/(start record)-(end record) - get records for a user</a></li>
37       <li><a href="#api_info">/info/(user) - Get information about a user</a></li>
38       <li><a href="#api_tag">/tag/(#|H|@)(tagname) - Retrieve records containing tags</a></li>
39       <li><a href="#api_subscribe">/subscribe/(user) - Subscribe to a user's updates</a></li>
40       <li><a href="#api_unsubscribe">/unsubscribe/(user) - Unsubscribe from a user's updates</a></li>
41       <li><a href="#api_feed">/feed - Get updates for subscribed users</a></li>
42       <li><a href="#api_feedinfo">/feedinfo, /feedinfo/(user) - Get subscription status</a></li>
43     </ul>
44   </li>
45   <li><a href="#design">Design</a>
46     <ul>
47       <li><a href="#motivation">Motivation</a></li>
48       <li><a href="#web_app_stack">Web App Stack</a></li>
49       <li><a href="#database">Database</a></li>
50       <li><a href="#subscriptions">Subscriptions</a></li>
51       <li><a href="#problems">Problems and Future Work</a></li>
52     </ul>
53   </li>
54 </ul>
55
56 <h2><a name="installing">Installing</a></h2>
57
58 <h3><a name="getting_the_source">Getting the source</a></h3>
59
60 <p>There's no stable release yet, but you can get everything currently
61 running on blerg.dominionofawesome.com by cloning the git repository at
62 http://git.bytex64.net/blerg.git.
63
64 <h3><a name="requirements">Requirements</a></h3>
65
66 <p>Blërg has varying requirements depending on how you want to run it
67 &mdash; as a standalone HTTP server, or as a CGI.  You will need:
68
69 <ul>
70 <li><a href="http://lloyd.github.com/yajl/">yajl</a> &gt;= 1.0.0
71 (yajl is a JSON parser/generator written in C which, by some twisted
72 sense of humor, requires ruby to compile)</li>
73 </ul>
74
75 <p>As a standalone HTTP, server, you will also need:
76
77 <ul>
78 <li><a href="http://www.gnu.org/software/libmicrohttpd/">GNU libmicrohttpd</a> &gt;= 0.9.3</li>
79 </ul>
80
81 <p>Or, as a CGI, you will need:
82
83 <ul>
84 <li><a href="http://www.newbreedsoftware.com/cgi-util/download/">cgi-util</a> &gt;= 2.2.1</li>
85 </ul>
86
87 <h3><a name="configuring">Configuring</a></h3>
88
89 <p>I know I'm gonna get shit for not using an autoconf-based system, but
90 I really didn't want to spend time figuring it out.  You should edit
91 libs.mk and put in the paths where you can find headers and libraries
92 for the above requirements.
93
94 <p>Also, further apologies to BSD folks &mdash; I've probably committed
95 several unconscious Linux-isms.  It would not surprise me if the
96 makefile refuses to work with BSD make, or if it fails to compile even
97 with gmake.  If you have patches or suggestions on how to make Blërg
98 more portable, I'd be happy to hear them.
99
100 <h3><a name="building">Building</a></h3>
101
102 <p>At this point, it should be gravy.  Type 'make' and in a few seconds,
103 you should have <code>blerg.httpd</code>, <code>blerg.cgi</code>,
104 <code>rss.cgi</code>, and <code>blergtool</code>.  Each of those can be
105 made individually as well, if you, for example, don't want to install
106 the prerequisites for <code>blerg.httpd</code> or
107 <code>blerg.cgi</code>.
108
109 <h3><a name="installing">Installing</a></h3>
110
111 <p>While it's not strictly required, Blërg will be easier to set up if
112 you configure it to work from the root of your website.  For this
113 reason, it's better to use a subdomain (i.e., blerg.yoursite.com is
114 easier than yoursite.com/blerg/).  If you do want to put it in a
115 subdirectory, you will have to modify <code>www/js/blerg.js</code> and
116 change baseURL at the top as well as a number of other self-references
117 in that file and <code>www/index.html</code>.  The CGI version should
118 work fine this way, but the HTTP version will require the request to be
119 rewritten, as it expects to be serving from the root.
120
121 <p>You cannot serve the database and client from different domains
122 (i.e., yoursite.com vs othersite.net, or even foo.yoursite.com and
123 bar.yoursite.com).  This is a requirement of the web browser &mdash; the
124 same origin policy will not allow an AJAX request to travel across
125 domains.
126
127 <h4>For the standalone web server:</h4>
128
129 <p>Right now, <code>blerg.httpd</code> doesn't serve any static assets,
130 so you're going to have to put it behind a real webserver like apache,
131 lighttpd, nginx, or similar.  Set the document root to the www
132 directory, then proxy /info, /create, /login, /logout, /get, /tag, and
133 /put to blerg.httpd.  You can change the port <code>blerg.httpd</code>
134 listens on in <code>config.h</code>.
135
136 <h4>For the CGI version:</h4>
137
138 <p>Copy the files in www/ to the root of your web server.  Copy
139 <code>blerg.cgi</code> to your web server.  Included in www-configs/ is
140 a .htaccess file for Apache that will rewrite the URLs.  If you need to
141 call the CGI something other than <code>blerg.cgi</code>, the .htaccess
142 file will need to be modified.
143
144 <h4>The extra RSS CGI</h4>
145
146 <p>There is an optional RSS cgi (<code>rss.cgi</code>) that will serve
147 RSS feeds for users.  Install this like <code>blerg.cgi</code> above.
148
149
150 <h2><a name="api">API</a></h2>
151
152 <p>Blërg's API was designed to be as simple as possible.  Data sent from
153 the client is POSTed with the application/x-www-form-urlencoded
154 encoding, and a successful response is always JSON.  The API endpoints
155 will be described as though the server were serving requests from the
156 root of the wesite.
157
158 <h3><a name="api_definitions">API Definitions</a></h3>
159
160 <p>On failure, all API calls return either a standard HTTP error
161 response, like 404 Not Found if a record or user doesn't exist, or a 200
162 response with a 'JSON failure', which will look like this:
163
164 <p><code>{"status": "failure"}</code>
165
166 <p>Blërg doesn't currently explain <i>why</i> there is a failure, and
167 I'm not sure it ever will.
168
169 <p>On success, you'll either get some JSON relating to your request (for
170 /get, /tag, or /info), or a 'JSON success' response (for /create, /put,
171 /login, or /logout), which looks like this:
172
173 <p><code>{"status": "success"}</code>
174
175 <p>For the CGI backend, you may get a 500 error if something goes wrong.
176 For the HTTP backend, you'll get nothing (since it will have crashed),
177 or maybe a 502 Bad Gateway if you have it behind another web server.
178
179 <p>All usernames must be 32 characters or less.  Usernames must contain
180 only the ASCII characters 0-9, A-Z, a-z, underscore (_), and hyphen (-).
181 Passwords can be at most 64 bytes, and have no limits on characters (but
182 beware: if you have a null in the middle, it will stop checking there
183 because I use <code>strncmp(3)</code> to compare).
184
185 <p>Tags must be 64 characters or less, and can contain only the ASCII
186 characters 0-9, A-Z, a-z, underscore (_), and hyphen (-).
187
188 <h3><a name="api_create">/create</a> - create a new user</a></h3>
189
190 <p>To create a user, POST to /create with <code>username</code> and
191 <code>password</code> parameters for the new user.  The server will
192 respond with JSON failure if the user exists, or if the user can't be
193 created for some other reason.  The server will respond with JSON
194 success if the user is created.
195
196 <h3><a name="api_login">/login</a> - log in</a></h3>
197
198 <p>POST to /login with the <code>username</code> and
199 <code>password</code> parameters for an existing user.  The server will
200 respond with JSON failure if the user does not exist or if the password
201 is incorrect.  On success, the server will respond with JSON success,
202 and will set a cookie named 'auth' that must be sent by the client when
203 accessing restricted API functions (/put and /logout).
204
205 <h3><a name="api_logout">/logout</a> - log out</a></h3>
206
207 <p>POST to /logout with with <code>username</code>, the user to log out,
208 along with the auth cookie in a Cookie header.  The server will respond
209 with JSON failure if the user does not exist or if the auth cookie is
210 bad.  The server will respond with JSON success after the user is
211 successfully logged out.
212
213 <h3><a name="api_put">/put</a> - add a new record</a></h3>
214
215 <p>POST to /put with <code>username</code> and <code>data</code>
216 parameters, and an auth cookie.  The server will respond with JSON
217 failure if the auth cookie is bad, if the user doesn't exist, or if
218 <code>data</code> contains more than 65535 bytes <i>after</i> URL
219 decoding.  The server will respond with JSON success after the record is
220 successfully added.
221
222 <h3><a name="api_get">/get/(user), /get/(user)/(start record)-(end record)</a> - get records for a user</a></h3>
223
224 <p>A GET request to /get/(user), where (user) is the user desired, will
225 return the last 50 records for that user in a list of objects.  The
226 record objects look like this:
227
228 <pre>
229 {
230   "record":"0",
231   "timestamp":1294309438,
232   "data":"eatin a taco on fifth street"
233 }
234 </pre>
235
236 <p><code>record</code> is the record number, <code>timestamp</code> is
237 the UNIX epoch timestamp (i.e., the number of seconds since Jan 1 1970
238 00:00:00 GMT), and <code>data</code> is the content of the record.  The
239 record number is sent as a string because while Blërg supports record
240 numbers up to 2<sup>64</sup> - 1, Javascript uses floating point for all
241 its numbers, and can only support integers without truncation up to
242 2<sup>53</sup>.  This difference is largely academic, but I didn't want
243 this problem to sneak up on anyone who is more insane than I am. :]
244
245 <p>The second form, /get/(user)/(start record)-(end record), retrieves a
246 specific range of records, from (start record) to (end record)
247 inclusive.  You can retrieve at most 100 records this way.  If (end
248 record) - (start record) specifies more than 100 records, or if the
249 range specifies invalid records, or if the end record is before the
250 start record, the server will respond with JSON failure.
251
252 <h3><a name="api_info">/info/(user)</a> - Get information about a user</a></h3>
253
254 <p>A GET request to /info/(user) will return a JSON object with
255 information about the user (currently only the number of records).  The
256 info object looks like this:
257
258 <pre>
259 {
260   "record_count": "544"
261 }
262 </pre>
263
264 <p>Again, the record count is sent as a string for 64-bit safety.
265
266 <h3><a name="api_tag">/tag/(#|H|@)(tagname)</a> - Retrieve records containing tags</a></h3>
267
268 <p>A GET request to this endpoint will return the last 50 records
269 associated with the given tag.  The first character is either # or H for
270 hashtags, or @ for mentions (I call them ref tags).  You should URL
271 encode the # or @, lest some servers complain at you.  The H alias for #
272 was created because Apache helpfully strips the fragment of a URL
273 (everything from the # to the end) before handing it off to the CGI,
274 even if the hash is URL encoded.  The record objects also contain an
275 extra <code>author</code> field, like so:
276
277 <pre>
278 {
279   "author":"Jon",
280   "record":"57",
281   "timestamp":1294555793,
282   "data":"I'm taking #garfield to the vet."
283 }
284 </pre>
285
286 <p>There is currently no support for getting more than 50 tags, but /tag
287 will probably mutate to work like /get.
288
289 <h3><a name="api_subscribe">/subscribe/(user)</a> - Subscribe to a
290 user's updates</a></h3>
291
292 <p>POST to /subscribe/(user) with a <code>username</code> parameter and
293 an auth cookie, where (user) is the user whose updates you wish to
294 subscribe to.  The server will respond with JSON failure if the auth
295 cookie is bad or if the user doesn't exist.  The server will respond
296 with JSON success after the subscription is successfully registered.
297
298 <h3><a name="api_unsubscribe">/unsubscribe/(user)</a> - Unsubscribe from
299 a user's updates</h3>
300
301 <p>Identical to /subscribe, but removes the subscription.
302
303 <h3><a name="api_feed">/feed</a> - Get updates for subscribed users</h3>
304
305 <p>POST to /feed, with a <code>username</code> parameter and an auth
306 cookie.  The server will respond with a JSON list of the last 50 updates
307 from all subscribed users, in reverse chronological order.  Fetching
308 /feed resets the new message count returned from /feedinfo.
309
310 <p>NOTE: subscription notifications are only stored while subscriptions
311 are active.  Any records inserted before or after a subscription is
312 active will not show up in /feed.
313
314 <h3><a name="api_feedinfo">/feedinfo, /feedinfo/(user)</a> - Get subscription
315 status for a user</a></h3>
316
317 <p>POST to /feedinfo with a <code>username</code> parameter and an auth
318 cookie to get general information about your subscribed feeds.
319 Currently, this only tells you how many new records there are since the
320 last time /feed was fetched.  The server will respond with a JSON
321 object:
322
323 <pre>
324 {"new":3}
325 </pre>
326
327 <p>POST to /feedinfo/(user) with a <code>username</code> parameter and
328 an auth cookie, where (user) is a user whose subscription status you are
329 interested in.  The server will respond with a simple JSON object:
330
331 <pre>
332 {"subscribed":true}
333 </pre>
334
335 <p>The value of "subscribed" will be either true or false depending on
336 the subscription status.
337
338 <h2><a name="design">Design</a></h2>
339
340 <h3><a name="motivation">Motivation</a></h3>
341
342 <p>Blërg was created as the result of a thought experiment: "What if
343 Twitter didn't need thousands of servers? What if its millions of users
344 could be handled by a single highly efficient server?"  This is probably
345 an unreachable goal due to the sheer amount of I/O, but we can certainly
346 try to do better.  Blërg was thus designed as a system with very simple
347 requirements:
348
349 <ol>
350 <li>Store and fetch small chunks of text efficiently</li>
351 <li>Create fast indexes for hash tags and @ mentions</li>
352 <li>Provide a HTTP interface web apps can use</li>
353 </ol>
354
355 <p>And to further simplify, I didn't bother handling deletes, full text
356 search, or more complicated tag searches.  Blërg only does the basics.
357
358 <h3><a name="web_app_stack">Web App Stack</a></h3>
359
360 <table class="pizzapie">
361 <tr><th>Classical model</th></tr>
362 <tr>
363   <td style="background-color: blue; color: white"><b>Client App</b><br>HTML/Javascript</td>
364 </tr>
365 <tr>
366   <td style="background-color: #9F0000; color: white"><b>Webserver</b><br>Apache, lighttpd, nginx, etc.</td>
367 </tr>
368 <tr>
369   <td style="background-color: #009F00; color: white"><b>Server App</b><br>Python, Perl, Ruby, etc.</td>
370 </tr>
371 <tr>
372   <td style="background-color: #404040; color: white"><b>Database</b><br>MySQL, PostgreSQL, MongoDB, CouchDB, etc.</td>
373 </tr>
374 </table>
375
376 <p>Modern web applications have at least a four-layer approach.  You
377 have the client-side browser app, the web server, the server-side
378 application, and the database.  Your data goes through a lot of layers
379 before it actually resides on disk somewhere (or, as they're calling it
380 these days, "The Cloud" *waves hands*).  Each of those layers requires
381 some amount of computing resources, so to increase throughput, we must
382 make the layers more efficient, or reduce the number of layers.
383
384 <table class="pizzapie">
385 <tr><th>Blërg model</th></tr>
386 <tr>
387   <td style="background-color: blue; color: white"><b>Blërg Client App</b><br>HTML/Javascript</td>
388 </tr>
389 <tr>
390   <td style="background-color: #404040; color: white"><b>Blërg Database</b><br>Fuckin' hardcore C and shit</td>
391 </tr>
392 </table>
393
394 <p>Blërg does both by smashing the last two or three layers into one
395 application.  Blërg can be run as either a standalone web server, or as
396 a CGI (FastCGI support is planned, but I just don't care right now).
397 Less waste, more throughput.  As a consequence of this, the entirety of
398 the application logic that the user sees is implemented in the client
399 app in Javascript.  That's why all the URLs have #'s &mdash; the page is
400 loaded once and switched on the fly to show different views, further
401 reducing load on the server.  Even parsing hash tags and URLs are done
402 in client JS.
403
404 <p>The API is simple and pragmatic.  It's not entirely RESTful, but is
405 rather designed to work well with web-based front-ends.  Client data is
406 always POSTed with the usual application/x-www-form-urlencoded encoding,
407 and server data is always returned in JSON format.
408
409 <p>The HTTP interface to the database idea has already been done by <a
410 href="http://couchdb.apache.org/">CouchDB</a>, though I didn't know that
411 until after I wrote Blërg. :)
412
413 <h3><a name="database">Database</a></h3>
414
415 <p>I was impressed by <a
416 href="http://www.varnish-cache.org/">varnish</a>'s design, so I decided
417 early in the design process that I'd try out mmaped I/O.  Each user in
418 Blërg has their own database, which consists of a metdata file, and one
419 or more data and index files.  The data and index files are memory
420 mapped, which hopefully makes things more efficient by letting the OS
421 handle when to read from disk (or maybe not &mdash I haven't benchmarked
422 it).  The index files are preallocated because I believe it's more
423 efficient than writing to it 40 bytes at a time as records are added.
424 The database's limits are reasonable:
425
426 <table class="statistics">
427 <tr><td>maximum record size</td><td>65535 bytes</td></tr>
428 <tr><td>maximum number of records per database</td><td>2<sup>64</sup> - 1 bytes</td></tr>
429 <tr><td>maximum number of tags per record</td><td>1024</td></tr>
430 <table>
431
432 <p>So as not to create grossly huge and unwieldy data files, the
433 database layer splits data and index files into many "segments"
434 containing at most 64K entries each.  Those of you doing some quick math
435 in your heads may note that this could cause a problem on 32-bit
436 machines &mdash; if a full segment contains entries of the maximum
437 length, you'll have to mmap 4GB (32-bit Linux gives each process only
438 3GB of virtual address space).  Right now, 32-bit users should change
439 <code>RECORDS_PER_SEGMENT</code> in <code>config.h</code> to something
440 lower like 32768.  In the future, I might do something smart like not
441 mmaping the whole fracking file.
442
443 <table class="bitstructure">
444 <tr><th>Record Index Structure</th></tr>
445 <tr><td class="B4">offset (32-bit integer)</td></tr>
446 <tr><td class="B2">length (16-bit integer)</td></tr>
447 <tr><td class="B2">flags (16-bit integer)</td></tr>
448 <tr><td class="B4">timestamp (32-bit integer)</td></tr>
449 </table>
450
451 <p>A record is stored by first appending the data to the data file, then
452 writing an entry in the index file containing the offset and length of
453 the data, as well as the timestamp.  Since each index entry is fixed
454 length, we can find the index entry simply by multiplying the record
455 number we want by the size of the index entry.  Upshot: constant-time
456 random-access reads and constant-time writes.  As an added bonus,
457 because we're using append-only files, we get lockless reads.
458
459 <table class="bitstructure">
460 <tr><th>Tag Structure</th></tr>
461 <tr><td class="B32">username (32 bytes)</td></tr>
462 <tr><td class="B8">record number (64-bit integer)</td></tr>
463 </table>
464
465 <p>Tags are handled by a separate set of indices, one per tag.  When a
466 record is added, it is scanned for tags, then entries are appended to
467 each tag index for the tags found.  Each index record simply stores the
468 user and record number.  Tags are searched by opening the tag file,
469 reading the last 50 entries or so, and then reading all the records
470 listed.  Voila, fast tag lookups.
471
472 <p>At this point, you're probably thinking, "Is that it?"  Yep, that's
473 it.  Blërg isn't revolutionary, it's just a system whose requirements
474 were pared down until the implementation could be made dead simple.
475
476 <p>Also, keeping with the style of modern object databases, I haven't
477 implemented any data safety (har har).  Blërg does not sync anything to
478 disk before returning success.  This should make Blërg extremely fast,
479 and totally unreliable in a crash.  But that's the way you want it,
480 right? :]
481
482 <h3><a name="subscriptions">Subscriptions</a></h3>
483
484 <p>When I first started thinking about the idea of subscriptions, I
485 immediately came up with the naïve solution: keep a list of users to
486 which users are subscribed, then when you want to get updates, iterate
487 over the list and find the last entries for each user.  And that would
488 work, but it's kind of costly in terms of disk I/O.  I have to visit
489 each user in the list, retrieve their last few entries, and store them
490 somewhere else to be sorted later.  And worse, that computation has to
491 be done every time a user checks their feed. As the number of users and
492 subscriptions grows, that will become a problem.
493
494 <p>So instead, I thought about it the other way around. Instead of doing
495 all the work when the request is received, Blërg tries to do as much as
496 possible by "pushing" updates to subscribed users.  You can think of it
497 kind of like a mail system.  When a user posts new content, a
498 notification is "sent" out to each of that user's subscribers.  Later,
499 when the subscribers want to see what's new, they simply check their
500 mailbox.  Checking your mailbox is usually a lot more efficient than
501 going around and checking everyone's records yourself, even with the
502 overhead of the "mailman."
503
504 <p>The "mailbox" is a subscription index, which is identical to a tag
505 index, but is a per-user construct.  When a user posts a new record, a
506 subscription index record is written for every subscriber.  It's a
507 similar amount of I/O as the naïve version above, but the important
508 difference is that it's only done once.  Retrieving records for accounts
509 you're subscribed to is then as simple as reading your subscription
510 index and reading the associated records.  This is hopefully less I/O
511 than the naïve version, since you're reading, at most, as many accounts
512 as you have records in the last N entries of your subscription index,
513 instead of all of them.  And as an added bonus, since subscription index
514 records are added as posts are created, the subscription index is
515 automatically sorted by time!  To support this "mail" architecture, we
516 also keep a list of subscribers and subscrib...ees in each account.
517
518 <h3><a name="problems">Problems, Caveats, and Future Work</a></h3>
519
520 <p>Blërg probably doesn't actually work like Twitter because I've never
521 actually had a Twitter account.
522
523 <p>I couldn't find a really good fast HTTP server library.
524 Libmicrohttpd is small, but it's focused on embedded applications, so it
525 often eschews speed for small memory footprint.  This is especially
526 apparent when you watch it chew through a POST request 300 bytes at a
527 time even though you've specified a buffer size of 256K.
528 <code>blerg.httpd</code> is still pretty fast this way &mdash; on my
529 2GHz Opteron 246, <a
530 href="http://www.joedog.org/index/siege-home">siege</a> says it serves a
531 690-byte /get request at about 945 transactions per second, average
532 response time 0.05 seconds, with 100 concurrent accesses &mdash; but a
533 fast HTTP server implementation could knock this out of the park.
534
535 <p>Libmicrohttpd is also really difficult to work with.  If you look at
536 the code, <code>http_blerg.c</code> is about 70% longer than
537 <code>cgi_blerg.c</code> simply because of all the iterator hoops I had
538 to jump through to process POST requests.  And if you can believe it, I
539 wrote <code>http_blerg.c</code> first. If I'd done it the other way
540 around, I probably would have given up on libmicrohttpd. :-/
541
542 <p>The data structures written to disk are dependent on the size and
543 endianness of the primitive data types on your architecture and OS.
544 This means that the databases are not portable.  A dump/import tool is
545 probably the easiest way to handle this.
546
547 <p>I do want to make a FastCGI version eventually, and this will
548 probably be a rather simple modification of cgi_blerg.
549
550 <p>Implementing deletes will be... interesting.  There is room in the
551 record index for a 'deleted' flag, but the problem is deleting any tags
552 referenced in the data.  This requires rescanning the record content and
553 putting a 'deleted' flag in the tag indices.  This will not be pretty,
554 so I'm just going to ignore it and hope nobody makes any mistakes. ;]
555
556 <p>Tag indices can grow arbitrarily large, which will cause problems for
557 32-bit machines around the 3GB mark.  Still, that's something like 80
558 million tags, so maybe it's not something to worry about.
559
560 <p>The API currently requires the client to transmit the user's password
561 in the clear.  A digest-based authentication scheme would be better,
562 though for real security, the app should run over HTTPS.
563
564 </body>
565 </html>