diff --git a/cache/cache.go b/cache/cache.go
index 569d9c7..e477ede 100644
--- a/cache/cache.go
+++ b/cache/cache.go
@@ -82,4 +82,8 @@ type Tx interface {
 	SetSeeder(t *models.Torrent, p *models.Peer) error
 	IncrementSlots(u *models.User) error
 	DecrementSlots(u *models.User) error
+	AddTorrent(t *models.Torrent) error
+	RemoveTorrent(t *models.Torrent) error
+	AddUser(u *models.User) error
+	RemoveUser(u *models.User) error
 }
diff --git a/cache/redis/redis.go b/cache/redis/redis.go
index 21ae0ea..7281f0f 100644
--- a/cache/redis/redis.go
+++ b/cache/redis/redis.go
@@ -14,7 +14,8 @@
 package redis
 
 import (
-	"encoding/json"
+	"errors"
+	"strconv"
 	"strings"
 	"time"
 
@@ -25,6 +26,18 @@ import (
 	"github.com/pushrax/chihaya/models"
 )
 
+var (
+	ErrCreateUser    = errors.New("redis: Incorrect reply length for user")
+	ErrCreateTorrent = errors.New("redis: Incorrect reply length for torrent")
+	ErrCreatePeer    = errors.New("redis: Incorrect reply length for peer")
+	ErrMarkActive    = errors.New("redis: Torrent doesn't exist")
+
+	SeederSuffix  = ":seeders"
+	LeecherSuffix = ":leechers"
+	TorrentPrefix = "torrent:"
+	UserPrefix    = "user:"
+)
+
 type driver struct{}
 
 func (d *driver) New(conf *config.DataStore) cache.Pool {
@@ -65,27 +78,12 @@ func (p *Pool) Close() error {
 
 func (p *Pool) Get() (cache.Tx, error) {
 	return &Tx{
-		conf:  p.conf,
-		done:  false,
-		multi: false,
-		Conn:  p.pool.Get(),
+		conf: p.conf,
+		done: false,
+		Conn: p.pool.Get(),
 	}, nil
 }
 
-// Tx represents a transaction for Redis with one gotcha:
-// all reads must be done prior to any writes. Writes will
-// check if the MULTI command has been sent to redis and will
-// send it if it hasn't.
-//
-// Internally a transaction looks like:
-// WATCH keyA
-// GET keyA
-// WATCH keyB
-// GET keyB
-// MULTI
-// SET keyA
-// SET keyB
-// EXEC
 type Tx struct {
 	conf  *config.DataStore
 	done  bool
@@ -101,27 +99,6 @@ func (tx *Tx) close() {
 	tx.Conn.Close()
 }
 
-func (tx *Tx) initiateWrite() error {
-	if tx.done {
-		return cache.ErrTxDone
-	}
-	if tx.multi != true {
-		tx.multi = true
-		return tx.Send("MULTI")
-	}
-	return nil
-}
-
-func (tx *Tx) initiateRead() error {
-	if tx.done {
-		return cache.ErrTxDone
-	}
-	if tx.multi == true {
-		panic("Tried to read during MULTI")
-	}
-	return nil
-}
-
 func (tx *Tx) Commit() error {
 	if tx.done {
 		return cache.ErrTxDone
@@ -153,163 +130,324 @@ func (tx *Tx) Rollback() error {
 	return nil
 }
 
-func (tx *Tx) FindUser(passkey string) (*models.User, bool, error) {
-	err := tx.initiateRead()
-	if err != nil {
-		return nil, false, err
+func createUser(userVals []string) (*models.User, error) {
+	if len(userVals) != 7 {
+		return nil, ErrCreateUser
 	}
-
-	key := tx.conf.Prefix + "user:" + passkey
-	_, err = tx.Do("WATCH", key)
+	// This could be a loop+switch
+	ID, err := strconv.ParseUint(userVals[0], 10, 64)
 	if err != nil {
-		return nil, false, err
+		return nil, err
 	}
-	reply, err := redis.String(tx.Do("GET", key))
+	Passkey := userVals[1]
+	UpMultiplier, err := strconv.ParseFloat(userVals[2], 64)
 	if err != nil {
-		if err == redis.ErrNil {
-			return nil, false, nil
-		}
-		return nil, false, err
+		return nil, err
 	}
-
-	user := &models.User{}
-	err = json.NewDecoder(strings.NewReader(reply)).Decode(user)
+	DownMultiplier, err := strconv.ParseFloat(userVals[3], 64)
 	if err != nil {
-		return nil, true, err
+		return nil, err
 	}
-	return user, true, nil
+	Slots, err := strconv.ParseInt(userVals[4], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	SlotsUsed, err := strconv.ParseInt(userVals[5], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	Snatches, err := strconv.ParseUint(userVals[6], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	return &models.User{ID, Passkey, UpMultiplier, DownMultiplier, Slots, SlotsUsed, uint(Snatches)}, nil
 }
 
-func (tx *Tx) FindTorrent(infohash string) (*models.Torrent, bool, error) {
-	err := tx.initiateRead()
-	if err != nil {
-		return nil, false, err
+func createTorrent(torrentVals []string, seeders map[string]models.Peer, leechers map[string]models.Peer) (*models.Torrent, error) {
+	if len(torrentVals) != 7 {
+		return nil, ErrCreateTorrent
 	}
+	ID, err := strconv.ParseUint(torrentVals[0], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	Infohash := torrentVals[1]
+	Active, err := strconv.ParseBool(torrentVals[2])
+	if err != nil {
+		return nil, err
+	}
+	Snatches, err := strconv.ParseUint(torrentVals[3], 10, 32)
+	if err != nil {
+		return nil, err
+	}
+	UpMultiplier, err := strconv.ParseFloat(torrentVals[4], 64)
+	if err != nil {
+		return nil, err
+	}
+	DownMultiplier, err := strconv.ParseFloat(torrentVals[5], 64)
+	if err != nil {
+		return nil, err
+	}
+	LastAction, err := strconv.ParseInt(torrentVals[6], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	return &models.Torrent{ID, Infohash, Active, seeders, leechers, uint(Snatches), UpMultiplier, DownMultiplier, LastAction}, nil
 
-	key := tx.conf.Prefix + "torrent:" + infohash
-	_, err = tx.Do("WATCH", key)
-	if err != nil {
-		return nil, false, err
-	}
-	reply, err := redis.String(tx.Do("GET", key))
-	if err != nil {
-		if err == redis.ErrNil {
-			return nil, false, nil
-		}
-		return nil, false, err
-	}
-
-	torrent := &models.Torrent{}
-	err = json.NewDecoder(strings.NewReader(reply)).Decode(torrent)
-	if err != nil {
-		return nil, true, err
-	}
-	return torrent, true, nil
 }
 
-func (tx *Tx) ClientWhitelisted(peerID string) (exists bool, err error) {
-	err = tx.initiateRead()
+// Prevents adding duplicate peers, and doesn't return error on dup add
+func (tx *Tx) addPeer(infohash string, peer *models.Peer, suffix string) error {
+	setKey := tx.conf.Prefix + TorrentPrefix + infohash + suffix
+	_, err := tx.Do("SADD", setKey, *peer)
 	if err != nil {
-		return false, err
-	}
-
-	key := tx.conf.Prefix + "whitelist"
-	_, err = tx.Do("WATCH", key)
-	if err != nil {
-		return
-	}
-
-	// TODO
-	return
-}
-
-func (tx *Tx) RecordSnatch(user *models.User, torrent *models.Torrent) error {
-	if err := tx.initiateWrite(); err != nil {
 		return err
 	}
-
-	// TODO
 	return nil
 }
 
-func (tx *Tx) MarkActive(t *models.Torrent) error {
-	if err := tx.initiateWrite(); err != nil {
+// Will not return an error if the peer doesn't exist
+func (tx *Tx) removePeer(infohash string, peer *models.Peer, suffix string) error {
+	setKey := tx.conf.Prefix + TorrentPrefix + infohash + suffix
+	_, err := tx.Do("SREM", setKey, *peer)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (tx *Tx) addPeers(infohash string, peers map[string]models.Peer, suffix string) error {
+	setKey := tx.conf.Prefix + TorrentPrefix + infohash + suffix
+	for _, peer := range peers {
+		err := tx.Send("SADD", setKey, peer)
+		if err != nil {
+			return err
+		}
+	}
+	tx.Flush()
+	tx.Receive()
+	return nil
+}
+
+func createPeer(peerString string) (*models.Peer, error) {
+	peerVals := strings.Split(strings.Trim(peerString, "{}"), " ")
+	if len(peerVals) != 9 {
+		return nil, ErrCreatePeer
+	}
+	ID := peerVals[0]
+	UserID, err := strconv.ParseUint(peerVals[1], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	TorrentID, err := strconv.ParseUint(peerVals[2], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	IP := peerVals[3]
+
+	Port, err := strconv.ParseUint(peerVals[4], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	Uploaded, err := strconv.ParseUint(peerVals[5], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	Downloaded, err := strconv.ParseUint(peerVals[6], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	Left, err := strconv.ParseUint(peerVals[7], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	LastAnnounce, err := strconv.ParseInt(peerVals[8], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	return &models.Peer{ID, UserID, TorrentID, IP, Port, Uploaded, Downloaded, Left, LastAnnounce}, nil
+
+}
+
+func (tx *Tx) getPeers(infohash string, suffix string) (peers map[string]models.Peer, err error) {
+	peers = make(map[string]models.Peer)
+	setKey := tx.conf.Prefix + TorrentPrefix + infohash + suffix
+	peerStrings, err := redis.Strings(tx.Do("SMEMBERS", setKey))
+	for peerIndex := range peerStrings {
+		peer, err := createPeer(peerStrings[peerIndex])
+		if err != nil {
+			return nil, err
+		}
+		peers[peer.ID] = *peer
+	}
+	return
+}
+
+func (tx *Tx) AddTorrent(t *models.Torrent) error {
+	hashkey := tx.conf.Prefix + TorrentPrefix + t.Infohash
+	_, err := tx.Do("HMSET", hashkey,
+		"id", t.ID,
+		"infohash", t.Infohash,
+		"active", t.Active,
+		"snatches", t.Snatches,
+		"up_multiplier", t.UpMultiplier,
+		"down_multiplier", t.DownMultiplier,
+		"last_action", t.LastAction)
+	if err != nil {
 		return err
 	}
 
-	// TODO
+	tx.addPeers(t.Infohash, t.Seeders, SeederSuffix)
+	tx.addPeers(t.Infohash, t.Leechers, LeecherSuffix)
+
+	return nil
+}
+
+func (tx *Tx) RemoveTorrent(t *models.Torrent) error {
+	hashkey := tx.conf.Prefix + TorrentPrefix + t.Infohash
+	_, err := tx.Do("HDEL", hashkey)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (tx *Tx) AddUser(u *models.User) error {
+	hashkey := tx.conf.Prefix + UserPrefix + u.Passkey
+	_, err := tx.Do("HMSET", hashkey,
+		"id", u.ID,
+		"passkey", u.Passkey,
+		"up_multiplier", u.UpMultiplier,
+		"down_multiplier", u.DownMultiplier,
+		"slots", u.Slots,
+		"slots_used", u.SlotsUsed,
+		"snatches", u.Snatches)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (tx *Tx) RemoveUser(u *models.User) error {
+	hashkey := tx.conf.Prefix + UserPrefix + u.Passkey
+	_, err := tx.Do("HDEL", hashkey)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (tx *Tx) FindUser(passkey string) (*models.User, bool, error) {
+	hashkey := tx.conf.Prefix + UserPrefix + passkey
+	userStrings, err := redis.Strings(tx.Do("HVALS", hashkey))
+	if err != nil {
+		return nil, false, err
+	} else if len(userStrings) == 0 {
+		return nil, false, nil
+	}
+	foundUser, err := createUser(userStrings)
+	if err != nil {
+		return nil, false, err
+	}
+	return foundUser, true, nil
+}
+
+func (tx *Tx) FindTorrent(infohash string) (*models.Torrent, bool, error) {
+	hashkey := tx.conf.Prefix + TorrentPrefix + infohash
+	torrentStrings, err := redis.Strings(tx.Do("HVALS", hashkey))
+	if err != nil {
+		return nil, false, err
+	} else if len(torrentStrings) == 0 {
+		return nil, false, nil
+	}
+
+	seeders, err := tx.getPeers(infohash, SeederSuffix)
+	leechers, err := tx.getPeers(infohash, LeecherSuffix)
+	foundTorrent, err := createTorrent(torrentStrings, seeders, leechers)
+	if err != nil {
+		return nil, false, err
+	}
+	return foundTorrent, true, nil
+}
+
+func (tx *Tx) ClientWhitelisted(peerID string) (exists bool, err error) {
+	key := tx.conf.Prefix + "whitelist"
+	return redis.Bool(tx.Do("ISMEMBER", key, peerID))
+}
+
+// This is a mulple action command, it's not internally atomic
+func (tx *Tx) RecordSnatch(user *models.User, torrent *models.Torrent) error {
+
+	torrentKey := tx.conf.Prefix + TorrentPrefix + torrent.Infohash
+	snatchCount, err := redis.Int(tx.Do("HINCRBY", torrentKey, 1))
+	if err != nil {
+		return err
+	}
+	torrent.Snatches = uint(snatchCount)
+
+	userKey := tx.conf.Prefix + TorrentPrefix + torrent.Infohash
+	snatchCount, err = redis.Int(tx.Do("HINCRBY", userKey, 1))
+	if err != nil {
+		return err
+	}
+	user.Snatches = uint(snatchCount)
+	return nil
+}
+
+func (tx *Tx) MarkActive(torrent *models.Torrent) error {
+	hashkey := tx.conf.Prefix + TorrentPrefix + torrent.Infohash
+	activeExists, err := redis.Int(tx.Do("HSET", hashkey, true))
+	if err != nil {
+		return err
+	}
+	// HSET returns 1 if hash didn't exist before
+	if activeExists == 1 {
+		return ErrMarkActive
+	}
 	return nil
 }
 
 func (tx *Tx) AddLeecher(t *models.Torrent, p *models.Peer) error {
-	if err := tx.initiateWrite(); err != nil {
-		return err
-	}
-
-	// TODO
-	return nil
+	return tx.addPeer(t.Infohash, p, LeecherSuffix)
 }
 
 func (tx *Tx) SetLeecher(t *models.Torrent, p *models.Peer) error {
-	if err := tx.initiateWrite(); err != nil {
-		return err
-	}
-
-	// TODO
-	return nil
+	return tx.addPeer(t.Infohash, p, LeecherSuffix)
 }
 
 func (tx *Tx) RemoveLeecher(t *models.Torrent, p *models.Peer) error {
-	if err := tx.initiateWrite(); err != nil {
-		return err
-	}
-
-	// TODO
-	return nil
+	return tx.removePeer(t.Infohash, p, LeecherSuffix)
 }
 
 func (tx *Tx) AddSeeder(t *models.Torrent, p *models.Peer) error {
-	if err := tx.initiateWrite(); err != nil {
-		return err
-	}
-
-	// TODO
-	return nil
+	return tx.addPeer(t.Infohash, p, SeederSuffix)
 }
 
 func (tx *Tx) SetSeeder(t *models.Torrent, p *models.Peer) error {
-	if err := tx.initiateWrite(); err != nil {
-		return err
-	}
-
-	// TODO
-	return nil
+	return tx.addPeer(t.Infohash, p, SeederSuffix)
 }
 
 func (tx *Tx) RemoveSeeder(t *models.Torrent, p *models.Peer) error {
-	if err := tx.initiateWrite(); err != nil {
-		return err
-	}
-
-	// TODO
-	return nil
+	return tx.removePeer(t.Infohash, p, SeederSuffix)
 }
 
 func (tx *Tx) IncrementSlots(u *models.User) error {
-	if err := tx.initiateWrite(); err != nil {
+	hashkey := tx.conf.Prefix + UserPrefix + u.Passkey
+	slotCount, err := redis.Int(tx.Do("HINCRBY", hashkey, 1))
+	if err != nil {
 		return err
 	}
-
-	// TODO
+	u.Slots = int64(slotCount)
 	return nil
 }
 
 func (tx *Tx) DecrementSlots(u *models.User) error {
-	if err := tx.initiateWrite(); err != nil {
+	hashkey := tx.conf.Prefix + UserPrefix + u.Passkey
+	slotCount, err := redis.Int(tx.Do("HINCRBY", hashkey, -1))
+	if err != nil {
 		return err
 	}
-
-	// TODO
+	u.Slots = int64(slotCount)
 	return nil
 }
 
diff --git a/cache/redis/redis_test.go b/cache/redis/redis_test.go
index f1a3cd4..3a18ea1 100644
--- a/cache/redis/redis_test.go
+++ b/cache/redis/redis_test.go
@@ -5,13 +5,8 @@
 package redis
 
 import (
-	"encoding/json"
-	"math/rand"
 	"os"
-	"strconv"
-	"strings"
 	"testing"
-	"time"
 
 	"github.com/garyburd/redigo/redis"
 
@@ -22,6 +17,7 @@ import (
 
 // Maximum number of parallel retries; depends on system latency
 const MAX_RETRIES = 9000
+
 const sample_infohash = "58c290f4ea1efb3adcb8c1ed2643232117577bcd"
 const sample_passkey = "32426b162be0bce5428e7e36afaf734ae5afb355"
 
@@ -39,6 +35,28 @@ func verifyErrNil(err error, t TestReporter) {
 	}
 }
 
+// Legacy JSON support for benching
+func (tx *Tx) initiateWrite() error {
+	if tx.done {
+		return cache.ErrTxDone
+	}
+	if tx.multi != true {
+		tx.multi = true
+		return tx.Send("MULTI")
+	}
+	return nil
+}
+
+func (tx *Tx) initiateRead() error {
+	if tx.done {
+		return cache.ErrTxDone
+	}
+	if tx.multi == true {
+		panic("Tried to read during MULTI")
+	}
+	return nil
+}
+
 func createTestTxObj(t TestReporter) *Tx {
 	testConfig, err := config.Open(os.Getenv("TESTCONFIGPATH"))
 	conf := &testConfig.Cache
@@ -71,22 +89,8 @@ func createTestTxObj(t TestReporter) *Tx {
 	return txObj
 }
 
-func createUserFromValues(userVals []string, t TestReporter) *models.User {
-	ID, err := strconv.ParseUint(userVals[0], 10, 64)
-	verifyErrNil(err, t)
-	Passkey := userVals[1]
-	UpMultiplier, err := strconv.ParseFloat(userVals[2], 64)
-	verifyErrNil(err, t)
-	DownMultiplier, err := strconv.ParseFloat(userVals[3], 64)
-	Slots, err := strconv.ParseInt(userVals[4], 10, 64)
-	verifyErrNil(err, t)
-	SlotsUsed, err := strconv.ParseInt(userVals[5], 10, 64)
-	verifyErrNil(err, t)
-	return &models.User{ID, Passkey, UpMultiplier, DownMultiplier, Slots, SlotsUsed}
-}
-
-func createUser() models.User {
-	testUser := models.User{214, "32426b162be0bce5428e7e36afaf734ae5afb355", 0.0, 0.0, 4, 2}
+func createTestUser() models.User {
+	testUser := models.User{214, "32426b162be0bce5428e7e36afaf734ae5afb355", 1.01, 1.0, 4, 2, 7}
 	return testUser
 }
 
@@ -105,7 +109,7 @@ func createLeechers() []models.Peer {
 	return testLeechers
 }
 
-func createTorrent() models.Torrent {
+func createTestTorrent() models.Torrent {
 
 	testSeeders := createSeeders()
 	testLeechers := createLeechers()
@@ -124,96 +128,6 @@ func createTorrent() models.Torrent {
 	return testTorrent
 }
 
-func createTorrentJson(t TestReporter) []byte {
-	jsonTorrent, err := json.Marshal(createTorrent())
-	verifyErrNil(err, t)
-	return jsonTorrent
-}
-
-func createUserJson(t TestReporter) []byte {
-	jsonUser, err := json.Marshal(createUser())
-	verifyErrNil(err, t)
-	return jsonUser
-}
-
-func ExampleJsonTransaction(testTx *Tx, retries int, t TestReporter) {
-	defer func() {
-		if err := recover(); err != nil {
-			t.Error(err)
-		}
-	}()
-	verifyErrNil(testTx.initiateRead(), t)
-	_, err := testTx.Do("WATCH", "testKeyA")
-	verifyErrNil(err, t)
-
-	_, err = redis.String(testTx.Do("GET", "testKeyA"))
-	if err != nil {
-		if err == redis.ErrNil {
-			t.Log("testKeyA does not exist yet")
-		} else {
-			t.Error(err)
-		}
-	}
-	_, err = testTx.Do("WATCH", "testKeyB")
-	verifyErrNil(err, t)
-
-	_, err = redis.String(testTx.Do("GET", "testKeyB"))
-	if err != nil {
-		if err == redis.ErrNil {
-			t.Log("testKeyB does not exist yet")
-		} else {
-			t.Error(err)
-		}
-	}
-
-	verifyErrNil(testTx.initiateWrite(), t)
-
-	// Generate random data to set
-	randGen := rand.New(rand.NewSource(time.Now().UnixNano()))
-	verifyErrNil(testTx.Send("SET", "testKeyA", strconv.Itoa(randGen.Int())), t)
-	verifyErrNil(testTx.Send("SET", "testKeyB", strconv.Itoa(randGen.Int())), t)
-
-	err = testTx.Commit()
-	// For parallel runs, there may be conflicts, retry until successful
-	if err == cache.ErrTxConflict && retries > 0 {
-		ExampleJsonTransaction(testTx, retries-1, t)
-		// Clear TxConflict, if retries max out, errors are already recorded
-		err = nil
-	} else if err == cache.ErrTxConflict {
-		t.Error("Conflict encountered, max retries reached")
-		t.Error(err)
-	}
-	verifyErrNil(err, t)
-}
-
-func ExampleJsonSchemaRemoveSeeder(torrent *models.Torrent, peer *models.Peer, t TestReporter) {
-	testTx := createTestTxObj(t)
-
-	verifyErrNil(testTx.initiateRead(), t)
-
-	key := testTx.conf.Prefix + "torrent:" + torrent.Infohash
-	_, err := testTx.Do("WATCH", key)
-	reply, err := redis.String(testTx.Do("GET", key))
-	if err != nil {
-		if err == redis.ErrNil {
-			t.Error("testTorrent does not exist")
-		} else {
-			t.Error(err)
-		}
-	}
-
-	verifyErrNil(json.NewDecoder(strings.NewReader(reply)).Decode(torrent), t)
-
-	delete(torrent.Seeders, "testPeerID2")
-
-	jsonTorrent, err := json.Marshal(torrent)
-	verifyErrNil(err, t)
-	verifyErrNil(testTx.initiateWrite(), t)
-	verifyErrNil(testTx.Send("SET", key, jsonTorrent), t)
-	verifyErrNil(testTx.Commit(), t)
-
-}
-
 func ExampleRedisTypeSchemaRemoveSeeder(torrent *models.Torrent, peer *models.Peer, t TestReporter) {
 	testTx := createTestTxObj(t)
 	setkey := testTx.conf.Prefix + "torrent:" + torrent.Infohash + ":seeders"
@@ -224,64 +138,70 @@ func ExampleRedisTypeSchemaRemoveSeeder(torrent *models.Torrent, peer *models.Pe
 	verifyErrNil(err, t)
 }
 
-func ExampleJsonSchemaFindUser(passkey string, t TestReporter) (*models.User, bool) {
-	testTx := createTestTxObj(t)
-
-	verifyErrNil(testTx.initiateRead(), t)
-
-	key := testTx.conf.Prefix + "user:" + passkey
-	_, err := testTx.Do("WATCH", key)
-	verifyErrNil(err, t)
-	reply, err := redis.String(testTx.Do("GET", key))
-	if err != nil {
-		if err == redis.ErrNil {
-			return nil, false
-		} else {
-			t.Error(err)
-		}
-	}
-
-	user := &models.User{}
-	verifyErrNil(json.NewDecoder(strings.NewReader(reply)).Decode(user), t)
-	return user, true
-}
-
 func ExampleRedisTypesSchemaFindUser(passkey string, t TestReporter) (*models.User, bool) {
 	testTx := createTestTxObj(t)
-	hashkey := testTx.conf.Prefix + "user_hash:" + sample_passkey
+	hashkey := testTx.conf.Prefix + UserPrefix + passkey
 	userVals, err := redis.Strings(testTx.Do("HVALS", hashkey))
-	if userVals == nil {
+	if len(userVals) == 0 {
 		return nil, false
 	}
 	verifyErrNil(err, t)
-	compareUser := createUserFromValues(userVals, t)
+	compareUser, err := createUser(userVals)
+	verifyErrNil(err, t)
 	return compareUser, true
 }
 
-func BenchmarkRedisJsonSchemaRemoveSeeder(b *testing.B) {
-	for bCount := 0; bCount < b.N; bCount++ {
-		b.StopTimer()
-		testTx := createTestTxObj(b)
-		testTorrent := createTorrent()
-		testSeeders := createSeeders()
-		key := testTx.conf.Prefix + "torrent:" + testTorrent.Infohash
-		// Benchmark setup not a transaction, not thread-safe
-		_, err := testTx.Do("SET", key, createTorrentJson(b))
-		verifyErrNil(err, b)
-		b.StartTimer()
+func TestFindUserSuccess(t *testing.T) {
+	testUser := createTestUser()
+	testTx := createTestTxObj(t)
+	hashkey := testTx.conf.Prefix + UserPrefix + sample_passkey
+	_, err := testTx.Do("DEL", hashkey)
+	verifyErrNil(err, t)
 
-		ExampleJsonSchemaRemoveSeeder(&testTorrent, &testSeeders[2], b)
+	err = testTx.AddUser(&testUser)
+	verifyErrNil(err, t)
+
+	compareUser, exists := ExampleRedisTypesSchemaFindUser(sample_passkey, t)
+
+	if !exists {
+		t.Error("User not found!")
+	}
+	if testUser != *compareUser {
+		t.Errorf("user mismatch: %v vs. %v", compareUser, testUser)
+	}
+}
+
+func TestFindUserFail(t *testing.T) {
+	compareUser, exists := ExampleRedisTypesSchemaFindUser("not_a_user_passkey", t)
+	if exists {
+		t.Errorf("User %v found when none should exist!", compareUser)
+	}
+}
+
+func TestAddGetPeers(t *testing.T) {
+
+	testTx := createTestTxObj(t)
+	testTorrent := createTestTorrent()
+
+	setkey := testTx.conf.Prefix + "torrent:" + testTorrent.Infohash + ":seeders"
+	testTx.Do("DEL", setkey)
+
+	testTx.addPeers(testTorrent.Infohash, testTorrent.Seeders, ":seeders")
+	peerMap, err := testTx.getPeers(sample_infohash, ":seeders")
+	if err != nil {
+		t.Error(err)
+	} else if len(peerMap) != len(testTorrent.Seeders) {
+		t.Error("Num Peers not equal")
 	}
 }
 
 func BenchmarkRedisTypesSchemaRemoveSeeder(b *testing.B) {
 	for bCount := 0; bCount < b.N; bCount++ {
-
 		// Ensure that remove completes successfully,
 		// even if it doesn't impact the performance
 		b.StopTimer()
 		testTx := createTestTxObj(b)
-		testTorrent := createTorrent()
+		testTorrent := createTestTorrent()
 		setkey := testTx.conf.Prefix + "torrent:" + testTorrent.Infohash + ":seeders"
 		testSeeders := createSeeders()
 		reply, err := redis.Int(testTx.Do("SADD", setkey,
@@ -300,37 +220,13 @@ func BenchmarkRedisTypesSchemaRemoveSeeder(b *testing.B) {
 	}
 }
 
-func BenchmarkRedisJsonSchemaFindUser(b *testing.B) {
-	// Ensure successful user find ( a failed lookup may have different performance )
-	b.StopTimer()
-	testTx := createTestTxObj(b)
-	testUser := createUser()
-	userJson := string(createUserJson(b))
-	verifyErrNil(testTx.initiateWrite(), b)
-	key := testTx.conf.Prefix + "user:" + sample_passkey
-	verifyErrNil(testTx.Send("SET", key, userJson), b)
-	verifyErrNil(testTx.Commit(), b)
-	b.StartTimer()
-	for bCount := 0; bCount < b.N; bCount++ {
-		compareUser, exists := ExampleJsonSchemaFindUser(sample_passkey, b)
-		b.StopTimer()
-		if !exists {
-			b.Error("User not found!")
-		}
-		if testUser != *compareUser {
-			b.Errorf("user mismatch: %v vs. %v", compareUser, testUser)
-		}
-		b.StartTimer()
-	}
-}
-
 func BenchmarkRedisTypesSchemaFindUser(b *testing.B) {
 
 	// Ensure successful user find ( a failed lookup may have different performance )
 	b.StopTimer()
-	testUser := createUser()
+	testUser := createTestUser()
 	testTx := createTestTxObj(b)
-	hashkey := testTx.conf.Prefix + "user_hash:" + sample_passkey
+	hashkey := testTx.conf.Prefix + UserPrefix + sample_passkey
 	reply, err := testTx.Do("HMSET", hashkey,
 		"id", testUser.ID,
 		"passkey", testUser.Passkey,
@@ -340,7 +236,7 @@ func BenchmarkRedisTypesSchemaFindUser(b *testing.B) {
 		"slots_used", testUser.SlotsUsed)
 
 	if reply == nil {
-		b.Error("no hash fields added!")
+		b.Log("no hash fields added!")
 	}
 	verifyErrNil(err, b)
 	b.StartTimer()
@@ -360,13 +256,6 @@ func BenchmarkRedisTypesSchemaFindUser(b *testing.B) {
 	}
 }
 
-func TestRedisTransaction(t *testing.T) {
-	for i := 0; i < 10; i++ {
-		// No retries for serial transactions
-		ExampleJsonTransaction(createTestTxObj(t), 0, t)
-	}
-}
-
 func TestReadAfterWrite(t *testing.T) {
 	// Test requires panic
 	defer func() {
@@ -392,100 +281,3 @@ func TestCloseClosedTransaction(t *testing.T) {
 	testTx.close()
 	testTx.close()
 }
-
-func TestParallelTx0(t *testing.T) {
-	if testing.Short() {
-		t.SkipNow()
-	}
-	t.Parallel()
-
-	for i := 0; i < 20; i++ {
-		go ExampleJsonTransaction(createTestTxObj(t), MAX_RETRIES, t)
-		time.Sleep(1 * time.Millisecond)
-	}
-
-}
-
-func TestParallelTx1(t *testing.T) {
-	if testing.Short() {
-		t.SkipNow()
-	}
-	t.Parallel()
-	ExampleJsonTransaction(createTestTxObj(t), MAX_RETRIES, t)
-	for i := 0; i < 100; i++ {
-		go ExampleJsonTransaction(createTestTxObj(t), MAX_RETRIES, t)
-	}
-}
-
-func TestParallelTx2(t *testing.T) {
-	if testing.Short() {
-		t.SkipNow()
-	}
-	t.Parallel()
-	for i := 0; i < 100; i++ {
-		go ExampleJsonTransaction(createTestTxObj(t), MAX_RETRIES, t)
-	}
-	ExampleJsonTransaction(createTestTxObj(t), MAX_RETRIES, t)
-}
-
-// Just in case the above parallel tests didn't fail, force a failure here
-func TestParallelInterrupted(t *testing.T) {
-	t.Parallel()
-
-	testTx := createTestTxObj(t)
-	defer func() {
-		if err := recover(); err != nil {
-			t.Errorf("initiateRead() failed in parallel %s", err)
-		}
-	}()
-	verifyErrNil(testTx.initiateRead(), t)
-
-	_, err := testTx.Do("WATCH", "testKeyA")
-	verifyErrNil(err, t)
-
-	testValueA, err := redis.String(testTx.Do("GET", "testKeyA"))
-	if err != nil {
-		if err == redis.ErrNil {
-			t.Log("redis.ErrNil")
-		} else {
-			t.Error(err)
-		}
-	}
-
-	_, err = testTx.Do("WATCH", "testKeyB")
-	if err != nil {
-		if err == redis.ErrNil {
-			t.Log("redis.ErrNil")
-		} else {
-			t.Error(err)
-		}
-	}
-
-	testValueB, err := redis.String(testTx.Do("GET", "testKeyB"))
-	if err != nil {
-		if err == redis.ErrNil {
-			t.Log("redis.ErrNil")
-		} else {
-			t.Error(err)
-		}
-	}
-	// Stand in for what real updates would do
-	testValueB = testValueB + "+updates"
-	testValueA = testValueA + "+updates"
-
-	// Simulating another client interrupts transaction, causing exec to fail
-	ExampleJsonTransaction(createTestTxObj(t), MAX_RETRIES, t)
-
-	verifyErrNil(testTx.initiateWrite(), t)
-	verifyErrNil(testTx.Send("SET", "testKeyA", testValueA), t)
-	verifyErrNil(testTx.Send("SET", "testKeyB", testValueB), t)
-
-	keys, err := (testTx.Do("EXEC"))
-	// Expect error
-	if keys != nil {
-		t.Error("Keys not nil; exec should have been interrupted")
-	}
-	verifyErrNil(err, t)
-
-	testTx.close()
-}
diff --git a/models/models.go b/models/models.go
index abfeeb6..cc1def7 100644
--- a/models/models.go
+++ b/models/models.go
@@ -40,4 +40,5 @@ type User struct {
 	DownMultiplier float64 `json:"down_multiplier"`
 	Slots          int64   `json:"slots"`
 	SlotsUsed      int64   `json:"slots_used"`
+	Snatches       uint    `json:"snatches"`
 }