/* * Copyright 2012, Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.facebook.LinkBench; import java.io.IOException; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.SortedSet; import java.util.TreeSet; /** * Simple in-memory implementation of GraphStore * Not efficient or optimized at all, just for testing purposes. * * MemoryLinkStore instances sharing the same data can be created * using the newInstance() method. * MemoryLinkStore can be accessed concurrently from multiple threads, * but a simple mutex is used so there is no internal concurrency (requests * are serialized) */ public class MemoryLinkStore extends GraphStore { private static class LinkLookupKey { final long id1; final long link_type; public LinkLookupKey(long id1, long link_type) { super(); this.id1 = id1; this.link_type = link_type; } @Override public boolean equals(Object other) { if (!(other instanceof LinkLookupKey)) { return false; } LinkLookupKey other2 = (LinkLookupKey)other; return id1 == other2.id1 && link_type == other2.link_type; } @Override public int hashCode() { return Long.valueOf(id1).hashCode() ^ Long.valueOf(link_type).hashCode(); } } /** Order links from most to least recent */ private static class LinkTimeStampComparator implements Comparator<Link> { @Override public int compare(Link l1, Link l2) { // ascending order of id1 if (l1.id1 != l2.id1) { if (l1.id1 < l2.id2) { return -1; } else { return 1; } } if (l1.time != l2.time) { // descending order of time if (l1.time < l2.time) { return 1; } else { return -1; } } // ascending order of id2 if (l1.id2 == l2.id2) { return 0; } else if (l1.id2 < l2.id2) { return -1; } else { return 1; } } } /** * Class for allocating IDs and storing objects */ private static class NodeDB { private long nextID; // Next id to allocate Map<Long, Node> data = new HashMap<Long, Node>(); /** Construct a new instance allocating ids from 1 */ NodeDB() { this(1); } NodeDB(long startID) { this.nextID = startID; } long allocateID() { return nextID++; } void reset(long startID) { nextID = startID; data.clear(); } } /** Simple implementation of LinkStore with nested maps and a set of * links sorted by timestamp: * dbid -> (id1, assoc_type) -> links */ private final Map<String, Map<LinkLookupKey, SortedSet<Link>>> linkdbs; private final Map<String, NodeDB> nodedbs; /** * Storage for objects */ private static final Comparator<Link> LINK_COMPARATOR = new LinkTimeStampComparator(); /** * Create a new MemoryLinkStore instance with fresh data */ public MemoryLinkStore() { this(new HashMap<String, Map<LinkLookupKey, SortedSet<Link>>>(), new HashMap<String, NodeDB>()); } /** * Create a new MemoryLinkStore handle sharing data with existing instance */ private MemoryLinkStore(Map<String, Map<LinkLookupKey, SortedSet<Link>>> linkdbs, Map<String, NodeDB> nodedbs) { this.linkdbs = linkdbs; this.nodedbs = nodedbs; } /** * Find a list of links based on * @param dbid * @param id1 * @param link_type * @param createPath if true, create nested structures. If false, return * null if map for (dbid, id, link_type) does not exist and * do not modify the structure; * @return */ private SortedSet<Link> findLinkByKey(String dbid, long id1, long link_type, boolean createPath) { Map<LinkLookupKey, SortedSet<Link>> db = linkdbs.get(dbid); if (db == null) { if (createPath) { // Autocreate db db = new HashMap<LinkLookupKey, SortedSet<Link>>(); linkdbs.put(dbid, db); } else { return null; } } LinkLookupKey key = new LinkLookupKey(id1, link_type); SortedSet<Link> links = db.get(key); if (links == null) { if (createPath) { links = newSortedLinkSet(); db.put(key, links); } else { return null; } } return links; } private TreeSet<Link> newSortedLinkSet() { return new TreeSet<Link>(LINK_COMPARATOR); } /** Create a new MemoryLinkStore sharing the same data structures as * this one */ public MemoryLinkStore newHandle() { return new MemoryLinkStore(linkdbs, nodedbs); } @Override public void initialize(Properties p, Phase currentPhase, int threadId) throws IOException, Exception { } @Override public void close() { } @Override public void clearErrors(int threadID) { } @Override public boolean addLink(String dbid, Link a, boolean noinverse) throws Exception { synchronized (linkdbs) { SortedSet<Link> links = findLinkByKey(dbid, a.id1, a.link_type, true); boolean exists = false; // Check for duplicates Iterator<Link> it = links.iterator(); while (it.hasNext()) { Link existing = it.next(); if (existing.id2 == a.id2) { it.remove(); exists = true; } } // Clone argument before inserting links.add(a.clone()); /*System.err.println(String.format("added link (%d, %d, %d), %d in list", a.id1, a.link_type, a.id2, links.size()));*/ return !exists; } } @Override public boolean deleteLink(String dbid, long id1, long link_type, long id2, boolean noinverse, boolean expunge) throws Exception { synchronized (linkdbs) { //NOTE: does not reclaim space from unused structures SortedSet<Link> linkSet = findLinkByKey(dbid, id1, link_type, false); if (linkSet != null) { Iterator<Link> it = linkSet.iterator(); while (it.hasNext()) { Link l = it.next(); if (l.id2 == id2) { if (!expunge) { l.visibility = VISIBILITY_HIDDEN; } else { it.remove(); } return true; // found it! } } } } return false; } @Override public boolean updateLink(String dbid, Link a, boolean noinverse) throws Exception { synchronized (linkdbs) { SortedSet<Link> linkSet = findLinkByKey(dbid, a.id1, a.link_type, false); if (linkSet != null) { Iterator<Link> it = linkSet.iterator(); while (it.hasNext()) { Link l = it.next(); if (l.id2 == a.id2) { it.remove(); linkSet.add(a.clone()); return true; } } } // Throw error if updating non-existing link throw new Exception(String.format("Link not found: (%d, %d, %d)", a.id1, a.link_type, a.id2)); } } @Override public Link getLink(String dbid, long id1, long link_type, long id2) throws Exception { synchronized (linkdbs) { SortedSet<Link> linkSet = findLinkByKey(dbid, id1, link_type, false); if (linkSet != null) { for (Link l: linkSet) { if (l.id2 == id2) { return l.clone(); } } } return null; } } @Override public Link[] getLinkList(String dbid, long id1, long link_type) throws Exception { return getLinkList(dbid, id1, link_type, 0, Long.MAX_VALUE, 0, rangeLimit); } @Override public Link[] getLinkList(String dbid, long id1, long link_type, long minTimestamp, long maxTimestamp, int offset, int limit) throws Exception { int skipped = 0; // used for offset synchronized (linkdbs) { SortedSet<Link> linkSet = findLinkByKey(dbid, id1, link_type, false); if (linkSet == null || linkSet.size() == 0) { return null; } else { // Do a first pass to find size of result array int matching = 0; for (Link l: linkSet) { if (l.visibility == VISIBILITY_DEFAULT && l.time >= minTimestamp && l.time <= maxTimestamp) { if (skipped < offset) { skipped++; continue; } matching++; if (matching >= limit) { break; } } } if (matching == 0) { return null; } Link res[] = new Link[matching]; // Iterate in desc order of timestamp, break ties by id2 int i = 0; skipped = 0; for (Link l: linkSet) { if (l.visibility == VISIBILITY_DEFAULT && l.time >= minTimestamp && l.time <= maxTimestamp) { if (skipped < offset) { skipped++; continue; } res[i++] = l; if (i >= limit) { break; } } } return res; } } } @Override public long countLinks(String dbid, long id1, long link_type) throws Exception { synchronized(linkdbs) { SortedSet<Link> linkSet = findLinkByKey(dbid, id1, link_type, false); if (linkSet == null) { return 0; } else { // Count the number of visible links int visible = 0; for (Link l: linkSet) { if (l.visibility == VISIBILITY_DEFAULT) { visible++; } } /*System.err.println( String.format("Lookup (%d, %d): %d visible, %d total", id1, link_type, visible, linkSet.size()));*/ return visible; } } } /** * Should be called with lock on nodedbs held * @param dbid * @param autocreate * @return */ private NodeDB getNodeDB(String dbid, boolean autocreate) throws Exception { NodeDB db = nodedbs.get(dbid); if (db == null) { if (autocreate) { db = new NodeDB(); nodedbs.put(dbid, db); } else { /* Not initialized.. can't autocreate since we don't know the desired * start ID */ throw new Exception("dbid " + dbid + " was not initialized"); } } return db; } @Override public void resetNodeStore(String dbid, long startID) { synchronized(nodedbs) { NodeDB db = nodedbs.get(dbid); if (db == null) { nodedbs.put(dbid, new NodeDB(startID)); } else { db.reset(startID); } } } @Override public long addNode(String dbid, Node node) throws Exception { synchronized(nodedbs) { NodeDB db = getNodeDB(dbid, false); long newId = db.allocateID(); // Put copy of node in map Node inserted = node.clone(); inserted.id = newId; Node prev = db.data.put(newId, inserted); if (prev != null) { throw new Exception("Internal error: node " + prev.toString() + " already existing in dbid " + dbid); } return newId; } } @Override public Node getNode(String dbid, int type, long id) throws Exception { synchronized(nodedbs) { NodeDB db = getNodeDB(dbid, false); Node n = db.data.get(id); if (n == null || n.type != type) { // Shouldn't return lookup on type mismatch return null; } else { return n.clone(); // return copy } } } @Override public boolean updateNode(String dbid, Node node) throws Exception { synchronized(nodedbs) { NodeDB db = getNodeDB(dbid, false); Node n = db.data.get(node.id); if (n == null || n.type != node.type) { // don't update on type mismatch return false; } else { // Store copy db.data.put(node.id, node.clone()); return true; } } } @Override public boolean deleteNode(String dbid, int type, long id) throws Exception { synchronized(nodedbs) { NodeDB db = getNodeDB(dbid, false); Node n = db.data.get(id); if (n == null || n.type != type) { // don't delete on type mismatch return false; } else { db.data.remove(id); return true; } } } }