Mischiefblog
I make apps for other people

General

Scalability Secrets: custom content that scales

Posted by Chris Jones
On July 15th, 2012 at 11:35

Permalink | Trackback | Links In |

Comments Off on Scalability Secrets: custom content that scales
Posted in General

Caveat: this isn’t about failover, security, or cloud computing.

Imagine you have 65 million registered users and you need to provide custom content for each (beyond “Hello, Bob”), say real time subscription content from tens of thousands of sources containing millions of posts.

Don’t try to show everything at once

You probably won’t be able to show everything to the user at once. Depending on your load and back-end systems, you may be lucky to simply let the registered user know that they have something to see.

Most of your users won’t be logged in

Most page views (at least to a homepage) won’t be on logged-in users: many will be first time users, or users who haven’t visited very often. You’ll need to hit your back-end servers with a specific ratio of total visits, something you can measure early, and use as a baseline for how many servers you need to scale.
(more…)

Query timing with SQLPlus

Posted by Chris Jones
On June 15th, 2012 at 10:59

Permalink | Trackback | Links In |

Comments Off on Query timing with SQLPlus
Posted in General

SQL Developer is a good tool for creating queries, but you should be using SQLPlus when trying to time queries. If you don’t already have SQLPlus installed you can get it from Oracle:

http://www.oracle.com/technetwork/database/enterprise-edition/downloads/112010-linx8664soft-100572.html

You want Oracle Database 11g Release 2 Client (11.2.0.1.0) for Linux x86-64. Unizip the client in your home directory.

Make sure you have the following environment variables defined:


# these point to wherever you have Oracle installed
ORACLE_HOME=~/oracle/product/11.1.0/client_1
LD_LIBRARY_PATH=~/oracle/product/11.1.0/client_1
PATH=$PATH:~/oracle/product/11.1.0/client_1

If you plan to run PL/SQL DDL/DML scripts, you’ll also need a SQLPATH environment variable which points to a directory where you keep the scripts.

SQLPATH=~/sqlscripts

If you don’t already have your database defined in your /etc/tnsnames.ora file (assuming you’re using that path and not something under /home/oracle), you’ll need to add the following TNS configuration:


dbalias =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = dbhost)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = dbname)
    )
  )

To run SQLPlus against your database as a specific user, use the following command line:

sqlplus username@dbalias

Enter the password when prompted and you’ll now be at the SQL> prompt.

To get script timings, enter the PL/SQL command:

SET TIMING ON

Avoid my house tomorrow night, children,…

Posted by Chris Jones
On October 31st, 2011 at 02:03

Permalink | Trackback | Links In |

Comments Off on Avoid my house tomorrow night, children,…
Posted in General

Avoid my house tomorrow night, children, for I will not be handing out chocolate. Enjoy your organic fruit snacks!

Moo hoo ha ha!

Hartford St, Salt Lake City, Utah

Home-made banana bread! I underestimated…

Posted by Chris Jones
On October 31st, 2011 at 00:53

Permalink | Trackback | Links In |

Comments Off on Home-made banana bread! I underestimated…
Posted in General

Home-made banana bread! I underestimated how much the raw dough would expand but it's still super yummy.

Hartford St, Salt Lake City, Utah

LMAX developed a 6 million TPS retail processor…

Posted by Chris Jones
On October 30th, 2011 at 18:48

Permalink | Trackback | Links In |

Comments Off on LMAX developed a 6 million TPS retail processor…
Posted in General

LMAX developed a 6 million TPS retail processor that runs on a single node. How?
* Using the correct data structures for the task at hand (don't use an ArrayList if you should be using a LinkedList)
* Developing data structures that take advantage of the JVM (i.e., a HashMap backed by primitive long type keys)
* Keeping the working state in memory (recovery via replay)
* Abandoning existing transactional software paradigms (actors, relational databases and transactions, queues, etc.)
* Concurrent unmarshallers and marshallers using a circular list of ring to provide natural ordering and locking (Disruptors, or a multicast graph of queues for parallel consumption)
* Designing business processes to output intermediate results when external resources are required (additional lookups, verifications, etc.)
* Performance testing and tuning with the real machine's performance in mind, not assumed performance

The LMAX Architecture

The LMAX Architecture. LMAX is a new retail financial trading platform. As a result it has to process many trades with low latency. The system is built on the JVM platform and centers on a Business Lo…

Non-blocking Node.js Here’s another one…

Posted by Chris Jones
On October 30th, 2011 at 17:50

Permalink | Trackback | Links In |

Comments Off on Non-blocking Node.js Here’s another one…
Posted in General

Non-blocking Node.js

Here's another one for the development toolbox: a collection of best practices and notes for non-blocking node.js development.

Node.js research Introduction Hi, I'm Ryan Wilcox. I've been

Hi, I'm Ryan Wilcox. I've been programming for about 15 years on various things, and been around the block a few times. I've done classic Mac OS applications, cross-platform applications i…

The Delightful web application pattern

Posted by Chris Jones
On September 22nd, 2011 at 16:39

Permalink | Trackback | Links In |

Comments Off on The Delightful web application pattern
Posted in General

Delightful, the Dynamic, Lightweight, RESTful web application pattern places rendering into the browser with Dynamic HTML (DHTML) and decouples display and application logic. The browser is responsible for laying out and rendering the application page, and fetches content from a REST service back end via JSON over AJAX. The REST service exposes the page content as URIs (essentially documents) and supports the full stable of HTTP actions.


(more…)

Python decorators for handler access control

Posted by Chris Jones
On July 14th, 2011 at 17:05

Permalink | Trackback | Links In |

Comments Off on Python decorators for handler access control
Posted in General

Given that a REST application receives context from the hosting web or application server (and unlike SOAP doesn’t try to perform authentication but accepts credentials as part of the context), it makes sense to keep the REST application a lightweight as possible. In this case, decorators can be used to check access control in addition to URI handler registration.

As a proof of concept (and to learn how to write one style of Python decorator), the following test demonstrates access control through decorators. In a real application, credentials would be based on group in addition to user name, potentially source IP or host, and multiple handlers could be called to resolve the request — consider how to chain handlers, especially where ordering is important, or if an output XML document or data structure (dictionary) would be sufficient to get around handler ordering.
(more…)

RIFT: thoughts and notes

Posted by Chris Jones
On February 21st, 2011 at 09:07

Permalink | Trackback | Links In |

Comments Off on RIFT: thoughts and notes
Posted in General

Unless you want random people joining your group, remember to make your group private — by default, you are a public group member. This won’t prevent you from joining public groups or raids at rift events or footholds.

The leveling experience is the same for all races on a given side. Leveling is linear: you don’t get different starting zones. This is unlike UO, EQ, WoW, DAOC, EVE, or Warhammer; it’s like EQ2 without the starting zone diversity.

The graphics will make you want to upgrade your PC: computers that run WoW at high settings without a hitch will play RIFT at half the framerate or at low settings. The GTX 260M on my laptop is entry level, while my wife’s GTX 460M handles it fairly well.

Low graphics settings

Low graphics settings

Books in the newbie garden: lore fiends need to make a point to hang out in the starting zone and patrol the ruins for limited books:

  • Durnes: Ranger
  • Elementalists, The First Mages
  • Journal of Zareph Mathos
  • The Unholy Blight: Reavers
  • Vachir Atlan: Champion

You won’t be able to get back into the Ruins of Mathosia (Guardian) or the future (Defiant) when you’re done, so if you want the books you need to take the time to pick them up.

Artifacts: as you make it into Silverwood, the first (Guardian) zone after the starter zone, you’ll rarely find artifacts. These can be collected (like EQ2 collections) and sold to an artifact collector for currency that can be used to purchase pets, masks, and a mount.

RIFT reuses a lot of gameplay mechanics you’ve seen in WoW:

  • You can’t put on armor in combat (you could in EQ)
  • Achievements (WoW borrowed from XBox)
  • Default keybinds (B for bag, C for character sheet, N for talents/Soul tree, O for social, P for skills)
  • You can build some characters to behave very similarly to WoW (Bladedancer is like a Combat Rogue, Ranger is like a Beastmaster Hunter, Paladin/Warlord is lika a Protection Paladin) — a lot of character classes are very different
  • 60% speed mounts used to be available at 20, now they’re available as soon as you can afford them (which will be about level 20).
  • There’s an auction house in Sanctum City.
  • Groups are made up of five player characters

You’ll need to retrain yourself to look for green exclamation points on your mini-map to find quests.

Rift events are fun: they’ve shaved off the public quest warts of WAR and made them enjoyable. Note that you can have higher level events trample through lower level questing zones (expecially in Silverwood) so you need to keep an eye out for the train of level 20 mobs.

RIFT invasion in Silverwood

It’s easy to find a rift: on areas of your map that you’ve explored, they’ll show up as icons. If you haven’t explored the area yet, you’ll be able to look up and find the telltale clouds and tentacles dropping from the sky.

Your healer or tank may not be quite what you expect: Rogues, Mages, and Priests all tank, while most Priests don’t heal unless they’ve picked up the appropriate soul.

Awesome EQ2 feature brought into RIFT: your character faces his target. When you have a conversation with someone, you can target his character and look him in the eye. It’s small but it helps tremendously with immersion.

The emotes are more like UO than WoW or EQ.

RIFT is incredibly group friendly. You should group.

You can level up from running rifts and invasions: follow the crowd, stay in the raid, and watch the XP and rewards roll in. Note that rifts scale by population, so as the game loses popularity or population is spread out to other zones, it will be harder to level using rifts exclusively.

End of RIFT beta

End of RIFT beta

Getting started with Berkeley Database Java Edition

Posted by Chris Jones
On January 17th, 2011 at 21:40

Permalink | Trackback | Links In |

Comments (1) |
Posted in General

What is a Berkeley Database and how is it different from Oracle or a relational database?

A Berkeley Database (BDB) is a key-value store, essentially a disk-based HashMap. DBMs store the keys as hashes in fixed size buckets.

Values in a DBM are typically accessed through put and get operations. This database model is reused in other databases:

  • The original Database Manager (DBM) was written in 1979 by Ken Thompson
  • Gnu DBM (GDBM), JDBM, and Tokyo Cabinet (among many others) are examples of key-value DBM implementations
  • BigTable is a large scale key-value store (with column attributes) that relies on GFS to split the database (something you normally have to do for yourself with disk-based DBMs)
  • Memcached is a memory-based key-value store used to cache slow lookups
  • Voldemort, Cassandra, and Dynamo distribute key-value data across a cluster of eventually consistent stores

DBMs are frequently used as the back-end for relational databases, frequently as indexes but also as row stores.

DBMs are not:

  • ISAM: they’re organized as a tree, not as a collection of fixed-length records
  • Relational: records in a DBM may point to other records, but the database itself does not enforce referential integrity
  • Columnar: keys are associated with one value, so additional values need to be placed in tokenizable strings
  • Value searchable: strictly speaking, a DBM should only be searched or retrieved by key
  • Schema based: they don’t have schemas so it’s up to the software to determine how records are related
  • SQL based: they don’t have a structured query language for lookups

DBMs are:

  • very fast
  • efficient
  • distributable (see Dynamo)
  • inexpensive (low CPU and memory overhead, depending on cache configuration)

A DBM assumes you know what key you want to retrieve and provides the minimum tools necessary to do so.

The world’s simplest DBM example

This is in Python. When working with a Python dictionary type, you normally initialize the dictionary and access members with a mapping operator ([]):

>>> d = {}
>>> d['a'] = 'apple'
>>> d['b'] = 'banana'
>>> d['c'] = 'cantaloupe'
>>> d['b']
'banana'

When working with a DBM in Python, the DBM library provides a dictionary implementation backed by the DBM implementation:

>>> import anydbm
>>> db = anydbm.open('mydb.dbm','c')
>>> db['a'] = 'apple'
>>> db['b'] = 'banana'
>>> db['c'] = 'cantaloupe'
>>> db.close()
>>> db = None
>>> db
>>> db = anydbm.open('mydb.dbm','c')
>>> db['b']
'banana'

In the second example:

  1. we imported the anydbm library (which looks for a suitable DBM implementation on the host),
  2. created (if is doesn’t already exist) the database file,
  3. populated the database with key-value pairs,
  4. closed and dereferenced the database to prove the values were persisted on disk,
  5. then reopened the database and retrieved a value the on-disk store.

Why would you use a DBM?

You use a DBM when:

  • you need to perform batch processing that will be prohibitively expensive with a SQL database
  • you’re working with very large or time-sensitive data sets
  • you’re trying to reduce your relational database (RDB) expense
  • you don’t have the time or resources to invest in RDB development (one-off development, application caches)
  • you want a searchable cache of non-relational values (such as an extracted index from a database)
  • you need to replicate the data set across many read-only hosts
  • you want to embed a database in your application (instead of making the application rely on an external database)
  • you’re scaling up and a RDB can’t scale with you

Choosing to use a DBM or RDB depends on your application, audience, load, knowledge, service level agreements, available hardware, etc. It’s a decision that’s not simply determined by software architecture but also by your organization’s goals, guidelines, and primary care architect’s input.

Getting started with Berkeley JE

Download the Berkeley JE from Oracle:

http://www.oracle.com/technetwork/database/berkeleydb/downloads/index.html

Install JE into a reasonable place and create a library for it with Eclipse (assuming you’re not using Maven).

Trivial Berkeley JE demonstration

package com.mischiefbox.bugged;

import java.io.File;

import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;

public class Bugged {
    final File envDir = new File("/home/chjones/tmp");
    final EnvironmentConfig envConfig = new EnvironmentConfig();
    final Environment env;
    final DatabaseConfig dbConfig = new DatabaseConfig();
    final Database db;

    public Bugged() {
        // set up the database
        envConfig.setTransactional(true);
        envConfig.setAllowCreate(true);

        env = new Environment(envDir, envConfig);

        dbConfig.setTransactional(true);
        dbConfig.setAllowCreate(true);
        dbConfig.setSortedDuplicates(true);

        db = env.openDatabase(null, "bugged", dbConfig);

        store();
        retrieve();

        db.close();
    }

    private void store() {
        // store a key/value pair
        byte [] firstKey = "firstKey".getBytes();
        byte [] firstValue = "firstValue".getBytes();
        DatabaseEntry keyEntry = new DatabaseEntry(firstKey);
        DatabaseEntry valueEntry = new DatabaseEntry(firstValue);

        try {
            Transaction txn = env.beginTransaction(null, null);
            OperationStatus status = db.put(txn, keyEntry, valueEntry);

            if (!status.equals(OperationStatus.SUCCESS)) {
                System.err.println("Error: " + status.toString());
            }

            txn.commit();
        } catch (DatabaseException e) {
            e.printStackTrace();
        }
    }

    private void retrieve() {
        // retrieve the value for the key
        byte [] key = "firstKey".getBytes();

        DatabaseEntry keyEntry = new DatabaseEntry(key);
        DatabaseEntry valueEntry = new DatabaseEntry();

        try {
            Transaction txn = env.beginTransaction(null, null);
            OperationStatus status = db.get(txn, keyEntry, valueEntry, LockMode.READ_UNCOMMITTED);

            if (!status.equals(OperationStatus.SUCCESS)) {
                System.err.println("Error: " + status.toString());
            } else {
                String value = new String(valueEntry.getData());
                if (value.equals("firstValue")) {
                    System.out.println("OK");
                } else {
                    System.err.println("Failed:  value returned '" + value + "' does not equal 'firstValue'");
                }
            }
        } catch (DatabaseException e) {
            e.printStackTrace();
        }
    }

    public static void main(String [] args) {
        new Bugged();
    }
}