Caluga
Rund um Java, coding und Hacking

Caluga - der Java Blog

Dieses Blog wird sich insbesondere mit Java und allen verwandten Themen beschäftigen, dabei besonders auch mit Morphium DEM Java POJO-Mapper für Mongodb


Suchergebnis: 62

<< 1 ... 2 ... 3 ... >>


Kategorie: MongoDB-POJO Mapper morphium --> Programmierung --> Computer

Additional Feature Release V2.2.10 morphium

Mo, 29. 09. 2014 - Tags:

New Release of Morphium V2.2.10

available at github

background

The last release was reported not to work properly in Tomcat or J2EE-Environments. The reason is the classpath scanning feature added with the last release. For type ids to work properly, morphium needs to scan the whole classpath for entities, in order to be able to determine the type of a db-object upon de-serialization.

This works fine, for applications and a like, but does have it's drawbacks in J2ee-Environments like a servlet engine (e.g. Tomcat, Jetty, Jboss).

In that environment you need to define a filter or some kind of startup hook, so that the additional classpath entries might be added to morphium.

If this does not happen, morphium tries to figure out the type by itself - but might fail, depending on your setup.

howto

It is recommended to make classpath elements known to morphium, using this call:

morphium.getMapper().getEntityCache().addClassPathElement("/opt/tomcat/webapps/myapp/WEB-INF/classes");

you need to add all pathes which might contain entities, pathes might be directories, .class files or jars.

In a webapp / servlet based application, you could easily do that to one of your filters, or other initialization code. Please keep in mind, that this will only work, if classpath is already set correctly.

Morphium already scans the "default" classpath upon startup, but in Webapps, this is changed. You could do something like this in order to get everything to work. In tomcat this might work:

for (Url u:((WebappClassLoader)getClass().getClassLoader()).getUrls()) {
   morphium.getMapper().getEntityCache().addClassPathElement(u.getFile());
}

Attention: This code might load classes, you don't want to be loaded. ;-)


Kategorie: MongoDB-POJO Mapper morphium --> Programmierung --> Computer

Feature Release of Morphium V2.2.9

So, 28. 09. 2014 - Tags:

ATTENTION: This version does not run properly in Tomcat or Jetty environment. We're working on a workaround...

New feature release of morphium 2.2.9. Available on github and via maven repository...

This version introduces type_ids. this is useful, if you want to be able to change Classnames and or refactor your code later (e.g. change packages etc.).

@Entity(type_id="MyType1")
public class MyEntity {

}

Usually morphium determins the name of the type from the query given. But when it comes to subdocuments and arrays or embedded maps, morphium needs to store the type information in mongo in order to be able to unmarshall the object.

Usually morphium uses the classname for this (which is the default type_id), but this breaks deserialization of objects, if class names or packages change. Every time you refactor something, you need to be sure that you do not break things.

Introducing type_ids solves this problem. It's recommended to use type ids for all of your Entities to be able to change your code as you like.

One additional info: If you change your classname, the default collection name will also change. So, to be able to do all refactorings you should define both a type id and a collection name:

@Entity(type_id="PersonEntity1", collection_name="persons"
public class MyInternalPersonRepresentation {

}

Attention: This feature makes it necessary, that morphium scans the classpath for entities and their type ids. If this causes problems on your setup, please let me know! (it only tested on MacOS X and linux for now). This might also slow down startup a bit (depending on your classpath)...

Migration to type ids

I was asked by a couple of users of morphium, how they could migrate to those new type IDs. you need to know, that you don't need to migrate at all if your java entities handled by morphium is not refactored. Morphium 2.2.9 is totally backward compatible with your data and you can safely upgrade anytime. But if you plan refactorings, it's not that hard to migrate:

Before doing the refactorings, run though all your data, that might have lists or maps containing entities, and add the corresponding type_id. This is type specific, of course.

You could also solve that iteratively by registering a MorphiumStorageListener either for a specific type (or types), or for all storage operations. Again this is very type dependent: some types do not need a change, some contain lists or maps, that need to be updated. (and you'd need to access mongo directly using morphium.getDatabase())

After you have prepared your database, you can update your codebase.


Kategorie: Computer --> Programmierung --> MongoDB-POJO Mapper morphium --> morphium

Morphium Doku V3.0

Fr, 05. 09. 2014 - Tags: morphium java mongo

There is no German Version available unfortunately - want help translating / documenting? Conctact us on github or via slack

Morphium Documentation

This documentation is refering to Morphium version [%morphium_version] and mongodb [%mongodb_version]. this documentation follows "MultiMarkdown" and was created using the MultiMarkdownComposer.

HTML Version here: MorphiumDoku If you just want to start right now, read [quick start]!

Ideas and concepts

When we started using MongoDB there was no fully capable POJO Mapper available. The only thing that was close to useable was Morphia (which is now developed by MongoDb. Unfortunately, Morphia had some issues, and lacked some features, we'd like to have, like (besides the usual features fast mapping, reliable query interface and so on):

  • Thread safety
  • cluster awareness
  • declarative caching
  • profiling support
  • support for partial updates
  • reference support incl. lazy loading of references
  • adaptable API (need to implement special POJO Mappings, Cache implementation change etc)
  • Cache synchronization in cluster
  • Validation
  • Declarative Index specification
  • Aggregation support

At that time there was nothing available providing all those features or what we could use as a basis to create those features (although we tried to implement that on base of Morphia - but the architecture of Morphia was not built for customization).

So, we started creating our own implementation and called it "Morphium" to honor the project "Morphia" which was the best around at that time.

But Morphium is a complete new Project, it was built totally from scratch. Even the POJO-Mapper is our own development (although there were some available at that point), but we had some special needs for Morphium's mapping.

The mapping takes place on a per-type basis. That means, usually (unless configured otherwise) the data of all objects of a certain type, will be stored in a corresponding collection.

In addition to that, the mapping is aware of object hierarchy and would even take annotations and settings into account, that are inherited.

Usually Morphium replaces camel case by underscore-separated strings. So an Object of type MyEntity would be stored in the collection my_entity. This behaviour can be configured as liked, you could even store all Objects in one collection. (see [Polymorphism])

Changes in Version 3.0

Motivation

Morphium 3.0 brings a lot improvements and changes, most of them are not really visible to the user, but unfortunately some of them make V3.x incompatible to V2.x.

The changes were triggered by the recent mongodb java driver update to also 3.0, which brings a whole new API. This API is (unfortunately also) not backward compatible[^not quite true, the driver contains both versions actually, but old API is usually marked deprecated]. This made it hard to add the changes in the official driver into morphium. Some of the changes made it also impossible to implement some features in morphium as it was before. So - the current implementation of morphium uses both old and new API - wich will break eventually.

The next step was, to be more independent from the driver, as those changes caused problems almost throughout the whole code of morphium. So, introducing with V3.0 of morphium, the driver is encapsulated deep within morphium.

Unfortunately, even the basic document representation changed[^old version used BasicDBObject, new version uses Document], which are very similar, but unfortunately represented in a whole new implementation of BSON[^binary json - details can be found here].

Also, we had some problems with dependencies in maven, causing to be several version of the mongodb driver being installed on production - which then caused some weird effects, most of them not really good ones ;-)

This made us reduce all dependency to the mongodb driver to a minimum - actually it is only used in the MorphiumDriver implementation for the official mongodb driver. But that also meant, we needed to get rid of all usages of ObjectID and BasicDBDocument and reduce usages of that into the driver implementation within morphium.

The question was - do we need to introduces some new object type for representing a Map<String,Object>? We thought no, so we changed the whole code in morphium, to internally use only standard Java8 API.

Yes, that is one feature also, since Morphium 3.0 we‘re running on java 8.

Changes

As you know the motivation now, these are the changes.

  • Driver encapsulated and configurable - you can now implement your own driver for usage with morphium
  • no usage of MongoDb classes, replaced by type MorphiumId and simple Map<String,Object> - this might actually break your code!
  • (soon) MongoDB Dependency in maven will be set to be provided, so that you can decide, which Version of the driver you want to use (or none...)
  • Morphium 3.0 includes some own implementation of drivers (mainly for testing purpose):
    • Driver: This is the Implementation of MorphiumDriver using the official Mongodb driver (V3.x)
    • InMemoryDriver: Not connecting to any mongo instance, just storing into memory. Good for testing. Does not support Aggregation!
    • SingleConnectDirectDriver: Just connecting to a master node, no failover. Useful if you do not have a replicaset
    • SingleConnectThreaddedDriver: Same as above, but uses a thread for reading the answers - slightly better performance in multithreaded environments, but only useful if you don't run a replicaSet
    • MetaDriver: A full featured implementation of the MorphiumDriver Interface, can be used as replacement for the mondogdb driver implementation. It uses a pool of SingleConnectThreaddedDriver to connect to mongodb.
  • Many changes in the internals
  • in references you can now specify the collection the reference should point to.
  • improvements in the internal caches, using the new improved features and performance of Java8[^see also here]
  • complete rewrite of the bulk operation handling
  • code improvements on many places, including some public interfaces (might break your code!)

quick start

Simple example on how to use Morphium:

First you need to create data to be stored in Mongo. This should be some simple class like this one here:

    @Entity
    public class MyEntity {
        @Id
        private MorphiumId myId;
        private int aField;
        private String other;
        private long property;
        //....  getter & setter here
    }

This given entity has a couple of fields which will be stored in Mongo according to their names. Usually the collection name is also derived from the ClassName (as most things in Morphium, that can be changed).

The names are usually translated from camel case (like aField) into lowercase with underscores (like a_field). This is the default behavior, but can be changed according to your needs.

In mongo the corresponding object would be stored in a collection named my_entity and would look like this:

    {
      _id: ObjectId("53ce59864882233112aa018df"),
      a_field: 123,
      other: "value"
    }

By default, null values are not serialized to mongo. So in this example, there is no field "property".

The next example shows how to store and access data from mongo:

    //creating connection 
    MorphiumConfig cfg=new MorphiumConfig()
    cfg.setHostSeed("localhost:27018", "mongo1","mongo3.home")
    //connect to a replicaset 
    //if you want to connect to a shared environment, you'd add the addresses of 
    //the mongos-servers here 
    //you can also specify only one of those nodes, 
    //Morphium (or better: mongodb driver) will figure out the others
    //connect 
    Morphium morphium=new Morphium(cfg);
    
    //Create an entity 
    MyEntity ent=new MyEntity()
    ent.setAField(123)
    ent.setOther("value")
    ent.setProperty(122l)
    morphium.store(ent);
    
    //the query object is used to access mongo 
    Query q=morphium.createQueryFor(MyEntity.class)
    q=q.f("a_field").eq(123)
    q=q.f("other").eq("value")
    q=q.f("property").lt(123).f("property").gt(100);
    
    List lst=q.asList();
    
    //or use iterator 
    for (MyEntity e:q.asIterable(100,2)) { 
        // iterate in windows of 100 objects 
        // 2 windows lookAhead 
    }

This gives a short glance of how Morphium works and how it can be used. But Morphium is capable of many more things...

Architecture

Morphium is built to be very flexible and can be used in almost any environment. So the architecture needs to be flexible and sustainable at the same time. Hence it's possible to use your own implementation for the cache if you want to.

There are four major components of Morphium:

  1. the Morphium Instance: This is you main entrypoint for interaction with Mongo. Here you create Queries and you write data to mongo. All writes will then be forwarded to the configured Writer implementation, all reads are handled by the Query-Object
  2. Query-Object: you need a query object to do reads from mongo. This is usually created by using Morphium.createQueryFor(Class<T> cls). With a Query, you can easily get data from database or have some things changed (update) and alike.
  3. the Cache: For every request that should be sent to mongo, Morphium checks first, whether this collection is to be cached and if there is already a batch being stored for the corresponding request.
  4. The Writers: there are 3 different types of writers in Morphium: The Default Writer (MorphiumWriter) - writes directly to database, waiting for the response, the BufferedWriter (BufferedWriter) - does not write directly. All writes are stored in a buffer which is then processed as a bulk. The last type of writer ist the asynchronous writer (AsyncWriter) which is similar to the buffered one, but starts writing immediately - only asynchronous. Morphium decides which writer to use depending on the configuration an the annotations of the given Entities. But you can always use asynchronous calls just by adding aAsyncCallback implementation to your request.

Simple rule when using Morphium: You want to read -> Use the Query-Object. You want to write: Use the Morphium Object.

There are some additional features built upon this architecture:

  • messaging: Morphium has a own messaging system.
  • cache synchronization: Synchronize caches in a clustered environment. Uses messaging
  • custom mappers - you can tell Morphium how to map a certain type from and to mongodb. For example there is a "custom" mapper implementation for mapping BigInteger instances to mongodb.
  • every of those implementations can be changed: it is possible to set the class name for the BufferedWriter to a custom built one (in MorphiumConfig). Also you could replace the object mapper with your own implementation by implementing the ObjectMapper interface and telling morphium which class to use instead. In short, these things can be changed in morphium / morphiumconfig:
    • MorphiumCache
    • ObjectMapper
    • Query
    • Field
    • QueryFactory
    • Driver (> V3.0)
  • Object Mapping from and to Strings (using the object mapper)

Configuring Morphium

First lets have a look on how to configure Morphium. As you already saw in the example in the last chapter, the configuration of Morphium ist encapsulated in one Object of type MorphiumConfig. This object has set some reasonable defaults for all settings. So it should be just as described above to use it.

Configuration Options

There are a lot of settings and customizations you can do within Morphium. Here we discuss all of them:

  • loggingConfigFile: can be set, if you want Morphium to configure your log4j for you. Morphium itself has a dependency to log4j (see Dependencies).
  • camelCaseConversion: if set to false, the names of your entities (classes) and fields won't be converted from camelcase to underscore separated strings. Default is true (convert to camelcase)
  • maxConnections: Maximum Number of connections to be built to mongo, default is 10
  • houseKeepingTimeout: the timeout in ms between cache housekeeping runs. Defaults to 5sec
  • globalCacheValidTime: how long are Cache entries valid by default in ms. Defaults to 5sek
  • writeCacheTimeout: how long to pause between buffered writes in ms. Defaults to 5sek
  • database: Name of the Database to connect to.
  • connectionTimeout: Set a value here (in ms) to specify how long to wait for a connection to mongo to be established. Defaults to 0 (⇒ infinite)
  • socketTimeout: how long to wait for sockets to be established, defaults to 0 as well
  • socketKeepAlive: if true, use TCP-Keepalive for the connection. Defaults to true
  • safeMode: Use the safe mode of mongo when set to true
  • globalFsync, globalJ: set fsync (file system sync) and j (journal) options. See mongo.org for more information
  • checkForNew: This is something interesting related to the creation of ids. Usually Ids in mongo are of type ObjectId. Anytime you write an object with an _id of that type, the document is either updated or inserted, depending on whether or not the ID is available or not. If it is inserted, the newly created ObjectId is being returned and add to the corresponding object. But if the id is not of type ObjectId, this mechanism will fail, no objectId is being created. This is no problem when it comes to new creation of objects, but with updates you might not be sure, that the object actually is new or not. If this obtion is set to true Morphium will check upon storing, whether or not the object to be stored is already available in database and would update. Attention: Morphium 3.0 removed the dependency from mogodb.org codebase and hence there is no ObjectId for POJOs anymore. You should replace these with the new MorphiumId.
  • writeTimeout: this timeout determines how long to wait until a write to mongo has to be finshed. Default is 0⇒ no timeout
  • maximumRetriesBufferedWriter: When writing buffered, how often should retry to write the data until an exception is thrown. Default is 10
  • retryWaitTimeBufferedWriter: Time to wait between retries
  • maximumRetriesWriter, maximumRetriesAsyncWriter: same as maximumRetriesBufferedWriter, but for direct storage or asynchronous store operation.
  • retryWaitTimeWriter, retryWaitTimeAsyncWriter: similar to retryWaitTimeBufferedWriter, but for the according writing type
  • globalW: W sets the number of nodes to have finished the write operation (according to your safe and j / fsync settings)
  • maxWaitTime: Sets the maximum time that a thread will block waiting for a connection.
  • writeBufferTime: Timeout for buffered writes. Default is 0
  • autoReconnect: if set to true connections are re-established, when lost. Default is true
  • maxAutoReconnectTime: how long to try to reconnect (in ms). Default is 0⇒ try as long as it takes
  • blockingThreadsMultiplier: There is a max number of connections to mongo, this factor determines the maximum number of threads that may be waiting for some connection. If this threshold is reached, new threads will get an Exception upon access to mongo.
  • mongoLogin,mongoPassword: User Credentials to connect to mongodb. Can be null.
  • mongoAdminUser, mongoAdminPwd: Credentials to do admin tasks, like get the replicaset status. If not set, use mongoLogin instead.
  • acceptableLatencyDifference: Latency between replicaset members still acceptable for reads.
  • autoValuesEnabled: Morphium supports automatic values being set to your POJO. These are configured by annotations (@LasChange, @CreationTime, @LastAccess, ...). If you want to switch this off globally, you can set it in the config. Very useful for test environments, which should not temper with productional data
  • readCacheEnabled: Globally disable readcache. This only affects entities with a @Cache annotation. By default it's enabled.
  • asyncWritesEnabled: Globally disable async writes. This only affects entities with a @AsyncWritesannotation
  • bufferedWritesEnabled: Globally disable buffered writes. This only affects entities with a @WriteBuffer annotation
  • defaultReadPreference: whether to read from primary, secondary or nearest by default. Can be defined with the @ReadPreference annotation for each entity.
  • replicaSetMonitoringTimeout: time interval to update replicaset status.
  • retriesOnNetworkError: if you happen to have an unreliable network, maybe you want to retry writes / reads upon network error. This settings sets the number of retries for that case.
  • sleepBetweenNetworkErrorRetries: set the time to wait between network error retries.
  • blockingThreadsMultiplier: Sets the multiplier for number of threads allowed to block waiting for a connection.

In addition to those settings describing the behavior of Morphium, you can also define custom classes to be used internally:

  • omClass: here you specify the class, that should be used for mapping POJOs (your entities) to DBOject. By Default it uses the ObjectMapperImpl. Your custom implementation must implement the interface ObjectMapper.
  • iteratorClass: set the Iterator implementation to use. By default MorphiumIteratorImplis being used. Your custom implementation must implement the interface MorphiumIterator
  • aggregatorClass: this is Morphium's representation of the aggregator framework. This can be replaced by a custom implementation if needed. Implements Aggregator interface
  • queryClass and fieldImplClass: this is used for Queries. If you want to take control over how queries ar built in Morphium and on how fields within queries are represented, you can replace those two with your custom implementation.
  • cache: Set your own implementation of the cache. It needs to implement the MorphiumCache interface. Default is MorphiumCacheImpl. You need to specify a fully configured cache object here, not only a class object.
  • driverClass: Set the driver implementation, you want to use. This is a string, set the class name here. E.g. morphiumconfig.setDriverClass(MetaDriver.class.getName()

Morphium Config Directly

The most straight foreward way of configuring Morphium is, using the object directly. This means you just call the getters and setters according to the given variable names above (like setMaxAutoReconnectTime()).

The minimum configuration is explained above: you only need to specify the database name and the host(s) to connect to. All other settings have sensible defaults, which should work for most cases.

Morphium Config From Property File

the configuration can be stored and read from a property object.

MorphiumConfig.fromProperties(Properties p); Call this method to set all values according to the given properties. You also can pass the properties to the constructor to have it configured.

To get the properties for the current configuration, just call asProperties() on a configured MorphiumConfig Object.

Here is an example property-file:

maxWaitTime=1000
maximumRetriesBufferedWriter=1
maxConnections=100
retryWaitTimeAsyncWriter=100
maxAutoReconnectTime=5000
blockingThreadsMultiplier=100
housekeepingTimeout=5000
hostSeed=localhost\:27017, localhost\:27018, localhost\:27019
retryWaitTimeWriter=1000
globalCacheValidTime=50000
loggingConfigFile=file\:/Users/stephan/morphium/target/classes/morphium-log4j-test.xml
writeCacheTimeout=100
connectionTimeout=1000
database=morphium_test
maximumRetriesAsyncWriter=1
maximumRetriesWriter=1
retryWaitTimeBufferedWriter=1000

The minimal property file would define only hosts and database. All other values would be defaulted.

If you want to specify classes in the config (like the Query Implementation), you neeed to specify the full qualified class name, e.g. de.caluga.morphium.customquery.QueryImpl

Morphium Config From Json File

The standard toString()method of MorphiumConfig creates an Json String representation of the configuration. to set all configuration options from a json string, just call createFromJson.

Documentation

Singleton Access

In some cases it's more convenient to use a singleton Instance to access Morphium. You don't need to implement a thread safe Morphium Singleton yourself, as Morphium does already have one.

The MorphiumSingleton is configured similar to the normal Morphium instance. Just set the config and you're good to go.

    MorphiumConfig config=new MorphiumConfig()//..configure it here
    MorphiumSingleton.setConfig(config);
    MorphiumSingleton.get().createQueryFor(MyEntity.class).f(...)

Connection to mongo and initializing of Morphium is done at the first call of get.

POJO Mapping

When talking about POJO Mapping, we're saying we marshall a POJO into a mongodb representation or we unmarshall the mongodb representation into a POJO.

Marshaling and unmarshalling is of utter importance for the functionality. It needs to take care of following things:

  • un/marshall every field. Easy if it’s a primitive datatype. Map to corresponding type in Monogo - mostly done by the mongodb java driver (or since 3.0 the MorphiumDriver implementation)
  • when it comes to lists and maps, examine every value. Maps may only have strings as keys (mongoldb limitation), un/marshall values
  • when a field contains a reference to another entity, take that into account. either store the
  • the POJO transformation needs to be 100% thread safe (Morphium itself is heavily multithreaded)

The ObjectMapper is the core of Morphium. It's used to convert every entity you want to store into a mongoldb document (java representation is a DBObject). Although it's one of the key things in Morphium it's still possible to make use of your own implementation (see chapter [Configuring Morphium]).

Querying Mongo

This is done by using the Query object. You need to create one for every entity you want to issue a query for. You could create one yourself, but the easiest way of doing so is calling the method .createQueryFor(Class class) in Morphium.

After that querying is very fluent. You add one option at a time, by default all conditions are AND-associated:

    Query q=morphium.createQueryFor(MyEntity.class);
    q=q.f("a_field").eq("Value");
    q=q.f("counter").lt(10);
    q=q.f("name").ne("Stephan").f("zip").eq("1234");

The f method stands for "field" and returns a Morphium internal representation of mongo fields. Threre you can call the operators, in our case it eq for equals, lt for less then and ne not equal. There are a lot more operators you might use, all those are defined in the MongoField interface:

    public Query all(List
    public Query eq(Object val);
    public Query ne(Object val);
    public Query size(int val);
    public Query lt(Object val);
    public Query lte(Object val);
    public Query gt(Object val);
    public Query gte(Object val);
    public Query exists();
    public Query notExists();
    public Query mod(int base, int val);
    public Query matches(Pattern p);
    public Query matches(String ptrn);
    public Query type(MongoType t);
    public Query in(Collection vals);
    public Query nin(Collection vals);
    
span class='java-comment'> /**
     * return a sorted list of elements around point x,y
     * spherical distance calculation
     *
     * @param x pos x
     * @param y pos y
     * @return the query
span class='java-comment'> */
    public Query nearSphere(double x, double y);
    
span class='java-comment'> /**
     * return a sorted list of elements around point x,y
     *
     * @param x pos x
     * @param y pos y
     * @return the query
span class='java-comment'> */
    public Query near(double x, double y);
    
span class='java-comment'> /**
     * return a sorted list of elements around point x,y
     * spherical distance calculation
     *
     * @param x pos x
     * @param y pos y
     * @return the query
span class='java-comment'> */
    public Query nearSphere(double x, double y, double maxDistance);
    
span class='java-comment'> /**
     * return a sorted list of elements around point x,y
     *
     * @param x pos x
     * @param y pos y
     * @return the query
span class='java-comment'> */
    public Query near(double x, double y, double maxDistance);

span class='java-comment'> /**
     * search for entries with geo coordinates wihtin the given rectancle - x,y upper left, x2,y2 lower right corner
span class='java-comment'> */
    public Query box(double x, double y, double x2, double y2);
    public Query polygon(double... p);
    public Query center(double x, double y, double r);
    
span class='java-comment'> /**
     * same as center() but uses spherical geometry for distance calc.
     *
     * @param x - pos x
     * @param y - y pos
     * @param r - radius
     * @return the query
span class='java-comment'> */
    public Query centerSphere(double x, double y, double r);
    
    public Query getQuery();
    public void setQuery(Query q);
    public ObjectMapper getMapper();
    public void setMapper(ObjectMapper mapper);
    public String getFieldString()
    public void setFieldString(String fld);

Query definitions can be in one line, or as above in several lines. Actually the current query object is changed with every call of f...something combination. The current object is always returned, for making the code more legible and understandable, you should assign the query as shown above. This makes clear: "The object changed"

If you need an "empty" query of the same type, you can call the method q. This method will return an empty query of the same type, using the same mapper etc. But only without conditions or something - just plain empty.

As already mentioned, the query by default creates AND-queries. If you need to create an or query, you can do so using the or method in the query object.

or takes a list of queries as argument, so a query might be built this way:

    Query q=morphium.createQueryFor(MyEntity.class);
    q=q.or(q.q().f("counter").le(10),q.q().f("name").eq("Morphium"));

This would create an OR-Query asking for all "MyEntities", that have a counter less than or equal to 10 OR whose name is "Morphium". You can add as much or-queries as you like. OR-Queries can actually be combined with and queries as well:

    Query q=morphium.createQueryFor(MyEntity.class);
    q=q.f("counter").ge(2);
    q=q.or(q.q().f("counter").le(10),q.q().f("name").eq("Morphium"));`

In that case, the query would be something like: counter is greater than 2 AND (counter is less then or equal to 10 OR name is "Morphium")

Combining and and or-queries is also possible, although the syntax would look a bit unfamiliar:

    Query q=morphium.createQueryFor(MyEntity.class);
    q=q.f("counter").lt(100).or(q.q().f("counter").mod(3,0),q.q().f("value").ne("v");

This would create a query returning all entries that do have a counter of less than 100 AND where the modulo to base 3 of the value counter equals 0, and the value of the field value equals "v".

Quite complex, eh?

Well, there is more to it... it is possible, to create a query using a "where"-String... there you can add JavaScript code for your query. This code will be executed at the mongodb node, executing your query:

    Query q=morphium.createQueryFor(MyEntity.class);
    q=q.where("this.counter > 10");

Attention: you can javascript code in that where clause, but you cannot access the db object there. This was changed when switching to Mongodb 2.6 with V8 Javascript engine

Declarative Caching

Using the @Cache annotation, you can define cache settings on a per type (= class) basis. This is done totally in background, handled by Morphium 100% transparently. You just add the annotation to your entities and you're good to go. See [Cache] and [Cache Synchronization]

Cache Synchronization

Cache synchronization was already mentioned above. The system of cache synchronization needs a messaging subsystem (see [Messaging] below). You just need to start the cache synchronizer yourself, if you want caches to be synchronized.

CacheSynchronizer cs=new CacheSynchronizer(morphium);
cs.start();

If you want to stop your cache synchronizing process, just call cs.setRunning(false); . The synchronizer will stop after a while (depending on your cache synchronization timeout).

By default no cache synchronizer is running.

Cluster Awareness

Morphium is cluster aware in a sense, that it does poll the state of a replicates periodically in order to know what nodes are life and need to be taken into account. (Same does the Java Driver, this information is now moved into the morphium driver implementation, so the double check is not necessary anymore).

Morphium also has support for clusters using it. Like a cluster of tomcats instances. In this case, Morphium is able to synchronize the caches of those cluster nodes.

Messaging

Morphium supports a simple Messaging system which uses mongoldb as storage. The messaging is more or less transactional (to the extend that mongo gives) and works multithreaded. To use messaging you only need to instantiate a Messaging-Instance. You may add listeners to this instance to process the messages and you may send messages through this instance.

Messaging is 100% multithreaded and thread safe.

Bulk Operations Support

All operations regarding lists (list updates, writing lists of objects, deleting lists of objects) will be implemented using the new bulk operation available since mongodb 2.6. This gives significant speed boost and adds reliability.

Actually, all method calls to mongo support a list of documents as argument. This means, you can send a list of updates, a list of documents to be inserted, a list of whatever. The ´BulkOperationContext´ only gathers those requests on the java side together, so that they can be sent in one call, instead of several.

With Morphium 3.0 an own implementation of this bulk operation context was introduced.

Callbacks

You can add a number of Listeners to Morphium in order to be informed about what happens, or to influence the way things are handled.

  • MorphiumStorageListeners: will be informed about any write process within morpheme. You can also veto if necessary. Works similar to [Lifecycle] methods, but for all entities.
  • CacheListener: Can be added to Morphium cache, will be informed about things to be added to cache, or if something would be updated or cleared. In all cases, a veto is possible.
  • ShutdownListener: if the system shuts down, you can be informed using this listener. It's not really Morphium specific.
  • ProfilingListener: will be informed about any read or write access to mongo and how long it took. This is useful if you want to track long requests or index misses.

In addition to that, almost all calls to mongo can be done asynchronously - either by defining that in the @Entity annotation or by defining it directly.

That means, an asList() call on a query object can take an AsyncCallback as argument, which then will be called, when the batch is ready. (which also means, the asList call will return null, the batch will be passed on in the callback).

Support for Aggregation

Morphium does have support for Aggregation in mongo. The aggregation Framework was introduced in mongo with V2.6 and is a alternative to MapReduce (which is still used). We implemented support for the new Aggregation framework into mongo. Up until now, there was no request for MapReduce - if you need it, please let me know.

Here is how the aggregation framework is used from mongo (see more info on the aggregation framework at MongoDb

This is the Unit test for Aggregation support in Mongo:

    @Test public void aggregatorTest() throws Exception { 
        createUncachedObjects(1000);
    
        Aggregator a = MorphiumSingleton.get().createAggregator(UncachedObject.class, Aggregate.class);
        assert (a.getResultType() != null);
        //eingangsdaten reduzieren
        a = a.project("counter");
        //Filtern
        a = a.match(MorphiumSingleton.get().createQueryFor(UncachedObject.class).f("counter").gt(100));
        //Sortieren - für $first/$last
        a = a.sort("counter");
        //limit der Daten
        a = a.limit(15);
        //group by - in dem Fall ALL, könnte auch beliebig sein
        a = a.group("all").avg("schnitt", "$counter").sum("summe", "$counter").sum("anz", 1).last("letzter", "$counter").first("erster", "$counter").end();
        //ergebnis projezieren 
        HashMap projection=new HashMap<>()
        projection.put("summe",1);
        projection.put("anzahl","$anz");
        projection.put("schnitt",1);
        projection.put("last","$letzter");
        projection.put("first","$erster");
        a = a.project(projection);
    
        List obj = a.toAggregationList();
        for (DBObject o : obj) {
            log.info("Object: " + o.toString());
        }
        List lst = a.aggregate();
        assert (lst.size() == 1) : "Size wrong: " + lst.size();
        log.info("Sum  : " + lst.get(0).getSumme());
        log.info("Avg  : " + lst.get(0).getSchnitt());
        log.info("Last :    " + lst.get(0).getLast());
        log.info("First:   " + lst.get(0).getFirst());
        log.info("count:  " + lst.get(0).getAnzahl());
    
    
        assert (lst.get(0).getAnzahl() == 15) : "did not find 15, instead found: " + lst.get(0).getAnzahl();
    
    }
    
    
     @Embedded 
     public static class Aggregate { 
        private double schnitt; 
        private long summe; 
        private int last; 
        private int first; 
        private int anzahl;
    
        @Property(fieldName = "_id")
        private String theGeneratedId;
    
        public int getAnzahl() {
            return anzahl;
        }
    
        public void setAnzahl(int anzahl) {
            this.anzahl = anzahl;
        }
    
        public int getLast() {
            return last;
        }
    
        public void setLast(int last) {
            this.last = last;
        }
    
        public int getFirst() {
            return first;
        }
    
        public void setFirst(int first) {
            this.first = first;
        }
    
        public double getSchnitt() {
            return schnitt;
        }
    
        public void setSchnitt(double schnitt) {
            this.schnitt = schnitt;
        }
    
        public long getSumme() {
            return summe;
        }
    
        public void setSumme(long summe) {
            this.summe = summe;
        }
    
        public String getTheGeneratedId() {
            return theGeneratedId;
        }
    
        public void setTheGeneratedId(String theGeneratedId) {
            this.theGeneratedId = theGeneratedId;
        }
    }

The class Aggregate is used to hold the batch of the aggregation.

Validation

If javax.validation can be found in class path, you are able to validate values of your entities using the validation annotations. Those validations will take place before the object would be saved.

Technically it's implemented as a JavaxValidationStorageListener which is a storage listener and vetoes the write operation if validation fails.

an example on how to use validation:

    @Id private MorphiumId id;
    
    @Min(3)
    @Max(7)
    private int theInt;
    
    @NotNull
    private Integer anotherInt;
    
    @Future
    private Date whenever;
    
    @Pattern(regexp = "m[ueü]nchen")
    private String whereever;
    
    @Size(min = 2, max = 5)
    private List friends;
    
    @Email
    private String email;
    

Those validation rules will be enforced upon storing the corresponding object:

    @Test(expected = ConstraintViolationException.class)
    public void testNotNull() {
        ValidationTestObject o = getValidObject();
        o.setAnotherInt(null);
        MorphiumSingleton.get().store(o);
    }

Polymorphism

Its possible to have different type of entities stored in one collection. Usually this will only make sense if those entities have some things in common. In an object oriented way: they are derived from one single entity.

In order to make this work, you have to tell Morphium that you want to use a certain entity in a polymorph way (property of the annotation @Entity). If so, the full qualified class name will be stored in the mongo document representing the entity. Actually, you can store any type of entity into one list, if each of those types is marked polymorph. Only reading them is a bit hard, as you would iterate over Objects and would have to decide on type yourself.

Async API

Fully Customizable

Description

on the following lines you get a more in depth view of the

Names of entities and fields

Morphium by defaults converts all java CamelCase identifiers in underscore separated strings. So, MyEntity will be stored in an collection called my_entity and the field aStringValue would be stored in as a_string_value.

When specifying a field, you can always use either the transformed name or the name of the corresponding java field. Collection names are always determined by the classname itself.

CamelCase conversion

But in Morphium you can of course change that behaviour. Easiest way is to switch off the transformation of CamelCase globally by setting camelCaseConversionEnabled to false (see above: Configuration). If you switch it off, its off completely - no way to do switch it on for just one collection or so.

If you need to have only several types converted, but not all, you have to have the conversion globally enabled, and only switch it off for certain types. This is done in either the @Entity or @Embedded annotation.

@Entity(convertCamelCase=false)
public class MyEntity {
   private String myField;`

This example will create a collection called MyEntity (no conversion) and the field will be called myField in mongo as well (no conversion).

Attention: Please keep in mind that, if you switch off camelCase conversion globally, nothing will be converted!

using the full qualified classname

you can tell Morphium to use the full qualified classname as basis for the collection name, not the simple class name. This would batch in createing a collection de_caluga_morphium_my_entity for a class called de.caluga.morphium.MyEntity. Just set the flag useFQN in the entity annotation to true.

@Entity(useFQN=true)
public class MyEntity {`

Recommendation is, not to use the full qualified classname unless it's really needed.

Specifying a collection / fieldname

In addition to that, you can define custom names of fields and collections using the corresponding annotation (@Entity, @Property).

For entities you may set a custom name by using the collectionName value for the annotation:

@Entity(collectionName="totallyDifferent") 
public class MyEntity {
    private String myValue;

the collection name will be totallyDifferent in mongo. Keep in mind that camel case conversion for fields will still take place. So in that case, the field name would probably be my_value. (if camel case conversion is enabled in config)

You can also specify the name of a field using the property annotation:

@Property(fieldName="my_wonderful_field")
private String something;`

Again, this only affects this field (in this case, it will be called my_wondwerful_field in mongo) and this field won't be converted camelcase. This might cause a mix up of cases in your mongodb, so please use this with care.

Accessing fields

When accessing fields in Morphium (especially for the query) you may use either the name of the Field in Java (like myEntity) or the converted name depending on the config (camelCased or not, or custom).

Using NameProviders

In some cases it might be necessary to have the collection name calculated dynamically. This can be acchieved using the NameProvider Interface.

You can define a NameProvider for your entity in the @Entity annotation. You need to specify the type there. By default, the NameProvider for all Entities is DefaultNameProvider. Which acutally looks like this:

    public final class DefaultNameProvider implements NameProvider {
    
    @Override
    public String getCollectionName(Class type, ObjectMapper om, boolean translateCamelCase, boolean useFQN, String specifiedName, Morphium morphium) {
 
        String name = type.getSimpleName();
    
        if (useFQN) {
            name = type.getName().replaceAll("\\.", "_");
        }
        if (specifiedName != null) {
            name = specifiedName;
        } else {
            if (translateCamelCase) {
                name = morphium.getARHelper().convertCamelCase(name);
            }
        }
        return name;
    }
    
    
    }
    

You can use your own provider to calculate collection names depending on time and date or for example depending on the querying host name (like: create a log collection for each server separately or create a collection storing logs for only one month each).

Attention: Name Provider instances will be cached, so please implement them threadsafe.

Entity Definition

Entitys in Morphium ar just "Plain old Java Objects" (POJOs). So you just create your data objects, as usual. You only need to add the annotation @Entity to the class, to tell Morphium "Yes, this can be stored". The only additional thing you need to take care of is the definition of an ID-Field. This can be any field in the POJO identifying the instance. Its best, to use MorphiumId as type of this field, as these can be created automatically and you don't need to care about those as well.

If you specify your ID to be of a different kind (like String), you need to make sure, that the String is set, when the object will be written. Otherwise you might not find the object again. So the shortest Entity would look like this:

@Entity
public class MyEntity {
   @Id private MorphiumId id;
   //.. add getter and setter here
}

indexes

Indexes are very important in mongo, so you should definitely define your indexes as soon as possible during your development. Indexes can be defined on the Entity itself, there are several ways to do so: - @Id always creates an index - you can add an @Index to any field to have that indexed:

@Index
private String name;
  • you can define combined indexes using the @Index annotation at the class itself:

    @Index({"counter, name","value,thing,-counter"} public class MyEntity {

This would create two combined indexes: one with counter and name (both ascending) and one with value, thing and descending counter. You could also define single field indexes using this annotations, but it`s easier to read adding the annotation direktly to the field.

  • Indexes will be created automatically if you create the collection. If you want the indexes to be created, even if there is already data stores, you need to callmorphium.ensureIndicesFor(MyEntity.class)- You also may create your own indexes, which are not defined in annotations by callingmorphium.ensureIndex(). As parameter you pass on a Map containing field name and order (-1 or 1) or just a prefixed list of strings (like"-counter","name").

Every Index might have a set of options which define the kind of this index. Like buildInBackground or unique. You need to add those as second parameter to the Index-Annotation:

@Entity
 @Index(value = {"-name, timer", "-name, -timer", "lst:2d", "name:text"}, 
            options = {"unique:1", "", "", ""})
public static class IndexedObject {

here 4 indexes are created. The first two ar more or less standard, wheres the lst index is a geospacial one and the index on name is a text index (only since mongo 2.6). If you need to define options for one of your indexes, you need to define it for all of them (here, only the first index is unique).

We're working on porting Morphium to java8, and there it will be possible to have more than one @Index annotation, making the syntax a bit more ledgeable

capped collections

Similar as with indexes, you can define you collection to be capped using the @Capped annotation. This annotation takes two arguments: the maximum number of entries and the maximum size. If the collection does not exist, it will be created as capped collection using those two values. You can always ensureCapped your collection, unfortunately then only the size parameter will be honored.

Querying

Querying is done via the Query-Object, which is created by Morphium itself (using the Query Factory). The definition of the query is done using the fluent interface:

    Query query=morphium.createQueryFor(MyEntity.class);
    query=query.f("id").eq(new MorphiumId());
    query=query.f("valueField").eq("the value");
    query=query.f("counter").lt(22);
    query=query.f("personName").matches("[a-zA-Z]+");
    query=query.limit(100).sort("counter");

In this example, I refer to several fields of different types. The Query itself is always of the same basic syntax:

    queryObject=queryObject.f(FIELDNAME).OPERATION(Value);
    queryObject=queryObject.skip(NUMBER)//skip a number of entreis
    queryObject=queryObject.limig(NUMBER)// limit batch
    queryObject.sort(FIELD_TO_SORTBY);  

As field name you may either use the name of the field as it is in mongo or the name of the field in java. If you specify an unknown field to Morphium, a RuntimeException will be raised.

For definition of the query, it's also a good practice to define enums for all of your fields. This makes it hard to have mistypes in a query:

    public class MyEntity {
      private MorphiumId id;
      private Double value;
      private String personName;
      private int counter;
      //.... field accessors
      public enum Fields { id, value, personName,counter, }
    }

There is a plugin for intelliJ creating those enums automatically. Then, when defining the query, you don't have to type in the name of the field, just use the field enum:

query=query.f(MyEntity.Fields.counter).eq(123);

After you defined your query, you probably want to access the data in mongo. Via Morphium,there are several possibilities to do that: - queryObject.get(): returns the first object matching the query, only one. Or null if nothing matched - queryObject.asList(): return a list of all matching objects. Reads all data in RAM. Useful for small amounts of data - Iterator<MyEntity> it=queryObject.asIterator(): creates a MorphiumIterator to iterate through the data, whch does not read all data at once, but only a couple of elements in a row (default 10).

the Iterators

Morphium has support for special Iterators, which steps through the data, a couple of elements at a time. By Default this is the standard behaviour. But the _Morphium_Iterator ist quite capable:

  • queryObject.asIterable() will step through the results batch by batch. The batch size is determined by the driver settings. This is the most performant, but lacks the ability to "step back" out of the current processed batch.
  • queryObject.asIterable(100) will step through the batch list, 100 at a time using a mongodb cursor iterator.
  • queryObject.asIterable(100,5) will step through the batch list, 100 at a time and keep 5 chunks of 100 elements each as prefetch buffers. Those will be filled in background.
  • queryObject.asIterable(100,1) actually the same as .asIterable(100) but using a query based iterator instead.
  • queryObject.asIterable(100, new PrefetchingIterator())): this is more or less the same as the prefetching above, but using the query based PrefetchingIterator. This is fetching the datachunks using skip and limit functionality of mongodb which showed some decrease in performance, the higher the skip is. It's still there for compatibility reasons.

Internally the default iterator does create queries that are derived from the sort of the query, if there is no sort specified, it will assume you want to sort by _id.

you could put each of those iterators to one of two classes:

  1. the iterator is using the Mongodb Cursor
  2. the iterator is using distinct queries for each step / chunk.

these have significant different behaviour.

query based iterators

the query based iterators use the usual query method of morphium. hence all related functionalities work, like caching, life cycle methods etc. It is just like you would create those queries in a row. one by one.

cursor based iterators

due to the fact that the query is being executed portion by portion, there is no way of having things cached properly. These queries do not use the cache!

Storing

Storing is more or less a very simple thing, just call morphium.store(pojo) and you're done. Although there is a bit more to it: - if the object does not have an id (id field is null), there will be a new entry into the corresponding collection. - if the object does have an id set (!= null), an update to db is being issued. - you can call morphium.storeList(lst) where lst is a list of entities. These would be stored in bulkd, if possible. Or it does a bulk update of things in mongo. Even mixed lists (update and inserts) are possible. Morphium will take care of sorting it out - there are additional methods for writing to mongo, like update operations set, unset, push, pull and so on (update a value on one entity or for all elements matching a query), delete objects or objects matching a query, and a like - The writer that acutally writes the data, is chosen depending on the configuration of this entity (see Annotations below)

Annotations

a lot of things can be configured in Morphium using annotations. Those annotations might be added to either classes, fields or both.

Entity

Perhaps the most important Annotation, as it has to be put on every class the instances of which you want to have stored to database. (Your data objects).

By default, the name of the collection for data of this entity is derived by the name of the class itself and then the camel case is converted to underscore strings (unless config is set otherwise).

These are the settings available for entities:

  • translateCamelCase: default true. If set, translate the name of the collection and all fields (only those, which do not have a custom name set)
  • collectionName: set the collection name. May be any value, camel case won't be converted.
  • useFQN: if set to true, the collection name will be built based on the full qualified class name. The Classname itself, if set to false. Default is false
  • polymorph: if set to true, all entities of this type stored to mongo will contain the full qualified name of the class. This is necessary, if you have several different entities stored in the same collection. Usually only used for polymorph lists. But you could store any polymorph marked object into that collection Default is false
  • nameProvider: specify the class of the name provider, you want to use for this entity. The name provider is being used to determine the name of the collection for this type. By Default it uses the DefaultNameProvider (which just uses the classname to build the collection name). see above

Embedded

Marks POJOs for object mapping, but don't need to have an ID set. These objects will be marshaled and unmarshaled, but only as part of another object (Subdocument). This has to be set at class level.

You can switch off camel case conversion for this type and determine, whether data might be used polymorph.

Capped

Valid at: Class level

Tells Morphium to create a capped collection for this object (see capped collections above).

Parameters:

maxSizemaximum size in byte. Is used when converting to a capped collection
maxNumbernumber of entries for this capped collection

AdditionalData

Special feature for Morphium: this annotation has to be added for at lease one field of type Map<String,Object>. It does make sure, that all data in Mongo, that cannot be mapped to a field of this entity, will be added to the annotated Map properties.

by default this map is read only. But if you want to change those values or add new ones to it, you can set readOnly=false

Aliases

It's possible to define aliases for field names with this annotation (hence it has to be added to a field).

 @Alias({"stringList","string_list"})
List<String> strLst;

in this case, when reading an object from Mongodb, the name of the field strLst might also be stringList or string_list in mongo. When storing it, it will always be stored as strLst or str_lst according to config.

This feature comes in handy when migrating data.

CreationTime

has to be added to both the class and the field(s) to store the creation time in. This value is set in the moment, the object is being stored to mongo. The data type for creation time might be:

  • long / Long: store as timestamp
  • Eate: store as date object
  • String: store as a string, you may need to specify the format for that

LastAccess

same as creation time, but storing the last access to this type. Attention: will cause all objects read to be updated and written again with a changed timestamp.

Usage: find out, which entries on a translation table are not used for quite some time. Either the translation is not necessary anymore or the corresponding page is not being used.

LastChange

Same as the two above, except the timestamp of the last change (to mongo) is being stored. The value will be set, just before the object is written to mongo.

DefaultReadPreference

Define the read preference level for an entity. This annotation has to be used at class level. Valid types are:

  • PRIMARY: only read from primary node
  • PRIMARY_PREFERED: if possible, use primary.
  • SECONDARY: only read from secondary node
  • SECONDARY_PREFERED: if possible, use secondary
  • NEAREST: I don't care, take the fastest

Id

Very important annotation to a field of every entity. It marks that field to be the id and identify any object. It will be stored as _id in mongo (and will get an index).

The Id may be of any type, though usage of ObjectId (or MorphiumId in Java) is strongly recommended.

Index

Define indexes. Indexes can be defined for a single field. Combined indexes need to be defined on class level. See above.

PartialUpdate

If this annotation is present for an entity, this entity would only send changes to mongo when being stored. This is useful for big objects, which only contain small changes.

Attention: in the background your object is being replaced by a Proxy-Object to collect the changes.

Property

Can be added to any field. This not only has documenting character, it also gives the opportunity to change the name of this field by setting the fieldName value. By Default the fieldName is ".", which means "fieldName based".

ReadOnly

Mark an entity to be read only. You'll get an exception when trying to store.

Reference

If you have a member variable, that is a POJO and not a simple value, you can store it as reference to a different collection, if the POJO is an Entity (and only if!).

This also works for lists and Maps. Attention: when reading Objects from disk, references will be de-referenced, which will batch into one call to mongo each.

Unless you set lazyLoading to true, in that case, the child documents will only be loaded when accessed.

transient

Do not store the field.

UseIfnull

Usually, Morphium does not store null values at all. That means, the corresponding document just would not contain the given field(s) at all.

Sometimes that might cause problems, so if you add @UseIfNull to any field, it will be stored into mongo even if it is null.

WriteOnly

Sometimes it might be useful to have an entity set to write only (logs). An exception will be raised, if you try to query such a entity.

WriteSafety

Sepcify the safety for this entity when it comes to writing to mongo. This can range from "NONE" to "WAIT FOR ALL SLAVES". Here are the available settings:

  • timeout: set a timeout in ms for the operation - if set to 0, unlimited (default). If set to negative value, wait relative to replication lag
  • level: set the safety level:
    • IGNORE_ERRORS None, no checking is done
    • NORMAL None, network socket errors raised
    • BASIC Checks server for errors as well as network socket errors raised
    • WAIT_FOR_SLAVE Checks servers (at lease 2) for errors as well as network socket errors raised
    • MAJORITY Wait for at least 50% of the slaves to have written the data
    • WAIT_FOR_ALL_SLAVES: waits for all slaves to have committed the data. This is depending on how many slaves are available in replica set. Wise timeout settings are important here. See WriteConcern in MongoDB Java-Driver for additional information

AsyncWrites

If this annotation is present at a given entity, all write access concerning this type would be done asynchronously. That means, the write process will start immediately, but run in background.

You won't be informed about errors or success. If you want to do that, you don't need to set @AsyncWrites, use one of the save method with a Callback for storing your data - those methods are all asynchronous.

WriteBuffer

Create a write buffer, do not write data directly to mongo, but wait for the buffer to be filled a certain amount:

  • size: default 0, max size of write Buffer entries, 0 means unlimited. STRATEGY is meaningless then
  • strategy: define what happens when write buffer is full and new things would be written. Can be one of WRITE_NEW, WRITE_OLD, IGNORE_NEW, DEL_OLD, JUST_WARN
    • WRITE_NEW: write all new incoming entries to the buffer directly to mongo, buffer won't grow
    • WRITE_OLD: take one of the oldest entries from the buffer, write it, queue the new entry to buffer. Buffer won't grow
    • IGNORE_NEW: do not add new entry to buffer and do not write it. Attention: possible data loss Buffer won't grow
    • DEL_OLD: delete an old entry from the buffer, add new one. Buffer won't grow
    • JUST_WARN: just issue a warning via log4j, but add the new Object anyway. Buffer will grow, no matter what threshold is set!

Cache

Read-Cache Settings for the given entity.

  • timeout: How long are entries in cache valid, in ms. Default 60000ms
  • clearOnWrite: if set to true (default) the cache will be cleared, when you store or update an instance of this type
  • maxEntries: Maximum number of entries in cache for this type. -1 means infinite
  • clearStrategy: when reaching the maximum number of entries, how to replace entries in cache.
    • LRU: remove the least recently used entry from cache, add the new
    • RANDOM: remove a random entry from cache, add the new
    • FIFO: remove the oldest entry from cache, add the new (default)
  • syncCache: Set the strategy for syncing cache entries of this type. This is useful when running in a clustered environment to inform all nodes of the cluster to change their caches accordingly. A sync message will be sent to all nodes using the Morphium messaging as soon as an item of this type is written to mongo.
    • NONE: No cache sync
    • CLEAR_TYPE_CACHE: clear the whole cache for this type on all nodes
    • REMOVE_ENTRY_FROM_TYPE_CACHE: remove an updated entry from the type cache of all nodes
    • UPDATE_ENTRY: update the entry in the cache on all nodes
    • This may cause heavy load on the messaging system. All sync strategies except CLEAR_TYPE_CACHE might batch in dirty reads on some nodes.

NoCache

Explicitly disable cache for this type. This is important if you have a hierarchy of entities and you want the "super entity" to be cached, but inherited entities from that type not.

Lifecycle

This is a marker annotation telling Morphium that in this type, there are some Lifecycle callbacks to be called.

Please keep in mind that all lifecycle annotations (see below) would be ignored, if this annotation is not added to the type.

PostLoad

If @Lifecycle is added to the type, @PostLoad may define the method to be called, after the object was read from mongo.

PreStore

If @Lifecycle is added to the type, @PreStore may define the method to be called, just before the object is written to mongo. It is possible to throw an Exception here to avoid storage of this object.

PostStore

If @Lifecycle is added to the type, @PostStore may define the method to be called, after the object was written to mongo.

PreRemove

If @Lifecycle is added to the type, @PreRemove may define the method to be called, just before the object would be removed from mongo. You might throw an exception here to avoid storage.

PostRemove

If @Lifecycle is added to the type, @PostRemove may define the method to be called, after the object was removed from mongo.

PreUpdate

If @Lifecycle is added to the type, @PreUpdate may define the method to be called, just before the object would be updated in mongo. Veto is possible by throwing an Exception.

PostUpdate

If @Lifecycle is added to the type, @PostUpdate may define the method to be called, after the object was updated in mongo.

Dependencies

Morphium does not have many dependencies:

  • log4j
  • mongo java driver (usually the latest version available at that time)
  • a simple json parser (json-simple)

Here is the excerpt from the pom.xml:

<dependency> 
  <groupid>cglib</groupid> 
  <artifactid>cglib</artifactid> 
  <version>2.2.2</version> 
</dependency> 
<dependency> 
  <groupid>log4j</groupid> 
  <artifactid>log4j</artifactid> 
  <version>1.2.17</version> 
</dependency>
<dependency> 
  <groupid>org.mongodb</groupid> 
  <artifactid>mongo-java-driver</artifactid> 
  <version>2.12.3</version> 
</dependency>
<dependency> 
  <groupid>com.googlecode.json-simple</groupid> 
  <artifactid>json-simple</artifactid> 
  <version>1.1</version> 
</dependency>

There is one kind of "optional" Dependency: If hibernate validation is available, it's being used. If it cannot be found in class path, it's no problem.

Code Examples

All those Code examples are part of the Morphium source distribution. All of the codes are at least part of a unit test.

Simple Write / Read

for (int i = 1; i <= NO_OBJECTS; i++) { 
    UncachedObject o = new UncachedObject(); 
    o.setCounter(i); 
    o.setValue("Uncached " + i % 2); 
    MorphiumSingleton.get().store(o); 
 } 
 Query<uncachedobject> q = MorphiumSingleton.get().createQueryFor(UncachedObject.class);
 q = q.f("counter").gt(0).sort("-counter", "value");
 List</uncachedobject><uncachedobject> lst = q.asList();
 assert (!lst.get(0).getValue().equals(lst.get(1).getValue()));

    q = q.q().f("counter").gt(0).sort("value", "-counter");
    List<UncachedObject> lst2 = q.asList();
    assert (lst2.get(0).getValue().equals(lst2.get(1).getValue()));
    log.info("Sorted");

    q = MorphiumSingleton.get().createQueryFor(UncachedObject.class);
    q = q.f("counter").gt(0).limit(5).sort("-counter");
    int st = q.asList().size();
    q = MorphiumSingleton.get().createQueryFor(UncachedObject.class);
    q = q.f("counter").gt(0).sort("-counter").limit(5);
    assert (st == q.asList().size()) : "List length differ?";

And:

Query<complexobject> q = MorphiumSingleton.get().createQueryFor(ComplexObject.class);

    q = q.f("embed.testValueLong").eq(null).f("entityEmbeded.binaryData").eq(null);
    String queryString = q.toQueryObject().toString();
    log.info(queryString);
    assert (queryString.contains("embed.test_value_long") && queryString.contains("entityEmbeded.binary_data"));
    q = q.f("embed.test_value_long").eq(null).f("entity_embeded.binary_data").eq(null);
    queryString = q.toQueryObject().toString();
    log.info(queryString);
    assert (queryString.contains("embed.test_value_long") && queryString.contains("entityEmbeded.binary_data"));

Asynchronous Write

@Test
public void asyncStoreTest() throws Exception {
    asyncCall = false;
    super.createCachedObjects(1000);
    waitForWrites();
    log.info("Uncached object preparation");
    super.createUncachedObjects(1000);
    waitForWrites();
    Query<UncachedObject> uc = MorphiumSingleton.get().createQueryFor(UncachedObject.class);
    uc = uc.f("counter").lt(100);
    MorphiumSingleton.get().delete(uc, new AsyncOperationCallback<Query<UncachedObject>>() {
        @Override
        public void onOperationSucceeded(AsyncOperationType type, Query<Query<UncachedObject>> q, long duration, List<Query<UncachedObject>> batch, Query<UncachedObject> entity, Object... param) {
            log.info("Objects deleted");
        }

        @Override
        public void onOperationError(AsyncOperationType type, Query<Query<UncachedObject>> q, long duration, String error, Throwable t, Query<UncachedObject> entity, Object... param) {
            assert false;
        }
    });

    uc = uc.q();
    uc.f("counter").mod(3, 2);
    MorphiumSingleton.get().set(uc, "counter", 0, false, true, new AsyncOperationCallback<UncachedObject>() {
        @Override
        public void onOperationSucceeded(AsyncOperationType type, Query<UncachedObject> q, long duration, List<UncachedObject> batch, UncachedObject entity, Object... param) {
            log.info("Objects updated");
            asyncCall = true;

        }

        @Override
        public void onOperationError(AsyncOperationType type, Query<UncachedObject> q, long duration, String error, Throwable t, UncachedObject entity, Object... param) {
            log.info("Objects update error");
        }
    });

    waitForWrites();

    assert MorphiumSingleton.get().createQueryFor(UncachedObject.class).f("counter").eq(0).countAll() > 0;
    assert (asyncCall);
}

Asynchronous Read

@Test
public void asyncReadTest() throws Exception {
    asyncCall = false;
    createUncachedObjects(100);
    Query<UncachedObject> q = MorphiumSingleton.get().createQueryFor(UncachedObject.class);
    q = q.f("counter").lt(1000);
    q.asList(new AsyncOperationCallback<UncachedObject>() {
        @Override
        public void onOperationSucceeded(AsyncOperationType type, Query<UncachedObject> q, long duration, List<UncachedObject> batch, UncachedObject entity, Object... param) {
            log.info("got read answer");
            assert (batch != null) : "Error";
            assert (batch.size() == 100) : "Error";
            asyncCall = true;
        }

        @Override
        public void onOperationError(AsyncOperationType type, Query<UncachedObject> q, long duration, String error, Throwable t, UncachedObject entity, Object... param) {
            assert false;
        }
    });
    waitForAsyncOperationToStart(1000000);
    int count = 0;
    while (q.getNumberOfPendingRequests() > 0) {
        count++;
        assert (count < 10);
        System.out.println("Still waiting...");
        Thread.sleep(1000);
    }
    assert (asyncCall);
}

Iterator

@Test
public void basicIteratorTest() throws Exception {
    createUncachedObjects(1000);

    Query<UncachedObject> qu = getUncachedObjectQuery();
    long start = System.currentTimeMillis();
    MorphiumIterator<UncachedObject> it = qu.asIterable(2);
    assert (it.hasNext());
    UncachedObject u = it.next();
    assert (u.getCounter() == 1);
    log.info("Got one: " + u.getCounter() + "  / " + u.getValue());
    log.info("Current Buffersize: " + it.getCurrentBufferSize());
    assert (it.getCurrentBufferSize() == 2);

    u = it.next();
    assert (u.getCounter() == 2);
    u = it.next();
    assert (u.getCounter() == 3);
    assert (it.getCount() == 1000);
    assert (it.getCursor() == 3);

    u = it.next();
    assert (u.getCounter() == 4);
    u = it.next();
    assert (u.getCounter() == 5);

    while (it.hasNext()) {
        u = it.next();
        log.info("Object: " + u.getCounter());
    }

    assert (u.getCounter() == 1000);
    log.info("Took " + (System.currentTimeMillis() - start) + " ms");
}

Messaging

@Test
public void messagingTest() throws Exception {
    error = false;

    MorphiumSingleton.get().clearCollection(Msg.class);

    final Messaging messaging = new Messaging(MorphiumSingleton.get(), 500, true);
    messaging.start();

    messaging.addMessageListener(new MessageListener() {
        @Override
        public Msg onMessage(Messaging msg, Msg m) {
            log.info("Got Message: " + m.toString());
            gotMessage = true;
            return null;
        }
    });
    messaging.storeMessage(new Msg("Testmessage", MsgType.MULTI, "A message", "the value - for now", 5000));

    Thread.sleep(1000);
    assert (!gotMessage) : "Message recieved from self?!?!?!";
    log.info("Dig not get own message - cool!");

    Msg m = new Msg("meine Message", MsgType.SINGLE, "The Message", "value is a string", 5000);
    m.setMsgId(new MorphiumId());
    m.setSender("Another sender");

    MorphiumSingleton.get().store(m);

    Thread.sleep(5000);
    assert (gotMessage) : "Message did not come?!?!?";

    gotMessage = false;
    Thread.sleep(5000);
    assert (!gotMessage) : "Got message again?!?!?!";

    messaging.setRunning(false);
    Thread.sleep(1000);
    assert (!messaging.isAlive()) : "Messaging still running?!?";
}

Cache Synchronization

@Test
public void cacheSyncTest() throws Exception {
    MorphiumSingleton.get().dropCollection(Msg.class);
    createCachedObjects(1000);

    Morphium m1 = MorphiumSingleton.get();
    MorphiumConfig cfg2 = new MorphiumConfig();
    cfg2.setAdr(m1.getConfig().getAdr());
    cfg2.setDatabase(m1.getConfig().getDatabase());

    Morphium m2 = new Morphium(cfg2);
    Messaging msg1 = new Messaging(m1, 200, true);
    Messaging msg2 = new Messaging(m2, 200, true);

    msg1.start();
    msg2.start();

    CacheSynchronizer cs1 = new CacheSynchronizer(msg1, m1);
    CacheSynchronizer cs2 = new CacheSynchronizer(msg2, m2);
    waitForWrites();

    //fill caches
    for (int i = 0; i < 1000; i++) {
        m1.createQueryFor(CachedObject.class).f("counter").lte(i + 10).asList(); //fill cache
        m2.createQueryFor(CachedObject.class).f("counter").lte(i + 10).asList(); //fill cache
    }
    //1 always sends to 2....


    CachedObject o = m1.createQueryFor(CachedObject.class).f("counter").eq(155).get();
    cs2.addSyncListener(CachedObject.class, new CacheSyncListener() {
        @Override
        public void preClear(Class cls, Msg m) throws CacheSyncVetoException {
            log.info("Should clear cache");
            preClear = true;
        }

        @Override
        public void postClear(Class cls, Msg m) {
            log.info("did clear cache");
            postclear = true;
        }

        @Override
        public void preSendClearMsg(Class cls, Msg m) throws CacheSyncVetoException {
            log.info("will send clear message");
            preSendClear = true;
        }

        @Override
        public void postSendClearMsg(Class cls, Msg m) {
            log.info("just sent clear message");
            postSendClear = true;
        }
    });
    msg2.addMessageListener(new MessageListener() {
        @Override
        public Msg onMessage(Messaging msg, Msg m) {
            log.info("Got message " + m.getName());
            return null;
        }
    });
    preSendClear = false;
    preClear = false;
    postclear = false;
    postSendClear = false;
    o.setValue("changed it");
    m1.store(o);

    Thread.sleep(1000);
    assert (!preSendClear);
    assert (!postSendClear);
    assert (postclear);
    assert (preClear);
    Thread.sleep(60000);

    long l = m1.createQueryFor(Msg.class).countAll();
    assert (l <= 1) : "too many messages? " + l;
//        createCachedObjects(50);
//        Thread.sleep(90000); //wait for messages to be cleared
//        assert(m1.createQueryFor(Msg.class).countAll()==0);
    cs1.detach();
    cs2.detach();
    msg1.setRunning(false);
    msg2.setRunning(false);
    m2.close();
}
@Test
public void nearTest() throws Exception {
    MorphiumSingleton.get().dropCollection(Place.class);
    ArrayList<Place> toStore = new ArrayList<Place>();
//        MorphiumSingleton.get().ensureIndicesFor(Place.class);
    for (int i = 0; i < 1000; i++) {
        Place p = new Place();
        List<Double> pos = new ArrayList<Double>();
        pos.add((Math.random() * 180) - 90);
        pos.add((Math.random() * 180) - 90);
        p.setName("P" + i);
        p.setPosition(pos);
        toStore.add(p);
    }
    MorphiumSingleton.get().storeList(toStore);

    Query<Place> q = MorphiumSingleton.get().createQueryFor(Place.class).f("position").near(0, 0, 10);
    long cnt = q.countAll();
    log.info("Found " + cnt + " places around 0,0 (10)");
    List<Place> lst = q.asList();
    for (Place p : lst) {
        log.info("Position: " + p.getPosition().get(0) + " / " + p.getPosition().get(1));
    }
}

@Index("position:2d")
@NoCache
@WriteBuffer(false)
@WriteSafety(level = SafetyLevel.MAJORITY)
@DefaultReadPreference(ReadPreferenceLevel.PRIMARY)
@Entity
public static class Place {
    @Id
    private MorphiumId id;

    public List<Double> position;
    public String name;

    public MorphiumId getId() {
        return id;
    }

    public void setId(MorphiumId id) {
        this.id = id;
    }

    public List<Double> getPosition() {
        return position;
    }

    public void setPosition(List<Double> position) {
        this.position = position;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

the problems with Logging

today there is a whole bunch of loggin frameworks. Every one is more capable than the other. Most commond probably are java.util.logging and log4j. Morphium used log4j quite some time. But in our high load environment we encountered problems with the logging itself. Also we had problems, that every library did use a different logging framework.

Morphium since V2.2.21 does use its own logger. This can be configured using Environment variables (in linux like export morphium_log_file=/var/log/morphium.log) or java system parameters (like java -Dmorphium.log.level=5).

This logger is built for performance and thread safety. It works find in high load environments. And has the following features:

  • it is instanciated with new - no singleton. Lesser performance / synchronization issues
  • it has several options for configuration. (see above). You can define global settings like morphium.log.file but you can also define settings for a prefix of a fqdn, like morphium.log.file.de.caluga.morphium. For example java -Dmorphium.log.level=2 -Dmorphium.log.level.de.caluga.morphium.messaging=5 would switch on debugging only for the messaging package, the default has level 2 (which is ERROR)
  • it is possible to define 3 Things in the way described above (either global or class / package sepcific): FileName (real path, or STDOUT or STDERR), Log level (0=none, 1=FATAL, 2=ERROR, 3=WARN, 4=INFO, 5=DEBUG) and whether the output should be synced or buffered (synced=false)
  • if you want to use log4j or java.util.logging as logging, you can set the log filename to log4j or jul accordingly
  • if you want to use your own logging implementation, just tell morphium the log delegate as filename, e.g. morphium.log.file=de.caluga.morphium.log.MyLogDelegate
  • of course, all this configuration can be done in code as well.

Swtiching to logback in V3.2.0

Yes, keeping an own addintional logger framework alive is not the smartest or easiest thing to do. So we decided to use logback for configuration of logging, using slf4j in morphium ourselves (in performance checks this seemed to have almost no negative impact fortunately)

So with upcoming V3.2.0 the own logger implementation is gone...


Kategorie: Allgemeines

New Release of Morphium V2.2.6

Di, 02. 09. 2014 - Tags:

New feature release V2.2.6:

This realease contains some bugfixes and features. All changes are 100% backward compatible.

  • Feature: MorphiumIterator is now multithreadded (if configurerd)
  • Feature: Messaging system is now multithreadded, increased performance (if configured)
  • Bugfix: Bugfix with MorphiumIterator in parallel processing code.
  • Bugfix: Fixed a bug with threadpools both in Writers and morphium iterator
  • Bugfix: fixes for MorphiumIterator in terms of Thread usage

you can download it on github or via maven repository...

Have fun...


Kategorie: Computer

New release V2.2.4 of #morphium - the #MongoDB POJO #mapper

Mi, 20. 08. 2014 - Tags:

New Release of Morphium 2.2.4

Including some minor bugfixes. Have a look at it here or download it via maven. Bugfixes include:

  • Storing lists of entities which are both updated and inserted as new into collection caused errors
  • When storing lists, the ObjectId was not set in the Entity
  • additional tests
  • minor improvements in documentation


Kategorie: Computer

New Morphium Release V2.2.3

Fr, 08. 08. 2014 - Tags:

Latest Version of Morphium Released: V2.2.3

This release is a feature release. It contains several new features and a lot of bugfixes.

  • Store timestamps (for @CreationTime, @LastChange and @LastAccess as Strings. The format can be defined in the corresponding annotation. Default is yyyy-MM-dd hh:mm:ss.

@CreationTime(format="hh:mm:ss")
public MyWonderfulEntity { .... @CreationTime
private String createdAt;
@CreationTime
private long created;
}

  • Have collections created capped or convert to capped using the new @Capped Annotation.
  • lots of bugfixes, especially with storing lists of entities efficiently.

You can check it out at http://sboesebeck.github.io/morphium/

PS: Mongodb still does not list morphium on their webpage sorry. I don't see, what the problem is, as Morphium is already productive on serveral projects. I sent a request to them, but did not get an answer yet.


Kategorie: Java --> Programmierung --> Computer

Neues Release von #Morphium V2.1.1 - DER #MongoDB POJO Mapper

Mi, 16. 04. 2014 - Tags:

Neues Feature release von Morphium

AktuelleVersion: V2.1.1.

  • Neues Feature CacheListener hinzugefügt. Nun kann man einfach die das Cachen von Elementen beeinflussen. der Listener kann bei Bedarf ein „Veto“ einlegen. Einfach seinen Listener im cache registrieren: morphium.getCache().addCacheListener(cacheListener);
  • Bugfixes, insbesondere im Cache
  • kleinere Verbesserungen am Code

V2.1.1 wurde soeben auf oss.sonatype hochgeladen, sollte in Kürze auch per maven repository verfügbar sein.

Weitere Informationen auf GitHub: <a href="http://sboesebeck from this source.github.io/morphium/">http://sboesebeck.github.io/morphium/

Hier noch das Cache Listener Interface:

 public interface CacheListener { public enum Operation { delete, store, update, }

    /**
     * ability to alter cached entries or avoid caching overall
     *
     * @param toCache - datastructure containing cache key and result
     * @param <T>     - the type
     * @return null, if not to cache
     */
    public <T> CacheObject<T> wouldAddToCache(CacheObject<T> toCache);

    public <T> boolean wouldClearCache(Class<T> affectedEntityType);

    public <T> boolean wouldRemoveEntryFromCache(Class cls, Object id, Object entity);


}


Kategorie: Allgemeines

New Release of #Morphium V2.1.1 - THE #MongoDB POJO Mapper

Mi, 16. 04. 2014 - Tags:

New Feature release of Morphium

Current Version is V2.1.1.

  • added new feature, CacheListeners. Now it’s easy to influence the caching of elements. Just add your listener to the Morphium-Cache morphium.getCache().addCacheListener(cacheListener)
  • bugfixes, especially in Caching
  • minor improvements in code quality

It was just uploaded to OSS tamiflu medicine.sonatype, should be available via maven soon.

For more info, take a look at the github page: http://sboesebeck.github.io/morphium/

Cache Listener Interface:

public interface CacheListener { public enum Operation { delete, store, update, }

    /**
     * ability to alter cached entries or avoid caching overall
     *
     * @param toCache - datastructure containing cache key and result
     * @param <T>     - the type
     * @return null, if not to cache
     */
    public <T> CacheObject<T> wouldAddToCache(CacheObject<T> toCache);

    public <T> boolean wouldClearCache(Class<T> affectedEntityType);

    public <T> boolean wouldRemoveEntryFromCache(Class cls, Object id, Object entity);

}


Kategorie: Allgemeines

Major #Morphium Release V2.1.0 for #MongoDb 2.6

Mi, 09. 04. 2014 - Tags:

New Release of Morphium V2.1.0

This release is specially rewritten for MongoDB 2.6 and MongoJavaDriver 2.12.0. Morphium internally uses Bulk Operations, where possible, to improve speed. In upcoming versions the Bulk operation feature will be exposed by morphium as well.

You can get it via maven repository or here


Neues Release von Morphium V2.1.0

Diese Version ist speziell auf Mongodb V2.6.0 abgestimmt und benutzt auch den aktuellsten Treiber V2.12.0. Intern nutzt Morphium auch die neuen BulkWriteOperations, wenn möglich bzw. sinnvoll. In den nächsten Verisonen von Morphium wird es auch eine API geben, um die Bulk-Operations über Morphium nutzen zu können.

Morphium kann über Maven Central Repository oder von hier bezogen werden. Da gibt es auch mehr Informationen darüber, wie man Morphium einsetzt.

Have fun!


Kategorie: Java --> Programmierung --> Computer

Morphium V2.0.27 #mogodb Object mapper

Di, 01. 04. 2014 - Tags:

I already posted that on twitter by it's own, but today I released a new version of Morphium, the full featured POJO-Object-Mapper for MongoDB. Latest changes include:

  • support for latest MongoDB Java Driver V2.11.4
  • improvement in ObjectMapper - Type-Variables of Lists may now be determined. So reading in on untyped lists is easier. Example: A member like List<MyEntity> may now de serialized correctly, even if the data does not contain a class name. Attention: works only on homogenous lists!
  • minor bugfixes, especially with messaging
  • minor improvements

I uploaded it quite some hours ago to OSS-Sonatype, it should be available in maven now.

One additional thing: I asked mongodb to list morphium on the Java Language Center. Until now the requests have simply been ignored. If some of you want to help, maybe you cann add a comment to this ticket: https://jira.mongodb.org/browse/DOCS-3017

Thanks.


Ich hab das schon auf Twitter gepostet, jetzt noch mal hier im Blog. Ich habe heute eine neue stabile Version von Morphium veröffentlicht. Morphium ist ein full featured POJO-Object-Mapper für MongoDB. Die letzten Änderungen umfassen unter anderem:

  • Update auf die aktuellste Java-Treiber Version V2.11.4
  • Verbesserungen im Object-Mapper. Die Typ-Variablen von generischen Listen werden nun ausgelesen und benutzt um Objekte zu de-serialisieren, wo der Klassenname nicht gesetzt ist. Jetzt werden listen der form List<MyEntity> korrekt deserialisiert, selbst wenn sie nicht mit Morphium erstellt wurde.
  • kleinere Bugfixes, insbesondere beim Messaging
  • kleinere Verbesserungen hier und da

Der Upload auf Sonatype OSS ist schon ein Weilchen her, sollte also auch im maven repository verfügbar sein.

Ein Ding noch in eigener Sache: Ich habe mongodb gebeten, morphium in deren Java-Language-Center auch aufzulisten. Bisher wurde diese Anfrage einfach ignoriert. Vielleicht habt ihr ja lust und gebt dem Ticket etwas nachdruck... https://jira.mongodb.org/browse/DOCS-3017

Danke!


Kategorie: Computer --> Programmierung --> Java

Anderes Character Encoding JDK7u45 vs Jdk7u4

Mi, 27. 11. 2013 - Tags: java programming

Das war heute etwas nervig. Ich musste wegen einem ssl-Bug in einem unsere Komponenten ein Update das Jul machen. Von JDK7u4 auf JDK7u45. Eigentlich würde ich da keine größeren Probleme erwarten. Das ganze ist auf ubuntu zwar etwas mehr als nur ein 'apt-get install' aber so schlimm nicht. Nach dem Setzen der richtigen alternatives für Java und javac gab es seltsame Fehler bei unseren Xsl Transformationen mit Saxon. Das lief vorher problemlos und nun gab es Probleme mit dem Character encoding. Ich habe das zunächst gar nicht in Verbindung gebracht, habe lange woanders gesucht, bis ich Testliste mal folgendes getan hab:

  • Transformation mit jdk7u45 Jvmund JAVA_HOME auf u45 -> character encoding Fehler bei Umlauten
  • Transformation mit jdk7u45 Jvm und JAVA_HOME auf u4 -> gleicher Fehler. Liegt nicht am jdk
  • Transformation mit jdk7u4 Jvm und JAVA_HOME auf u45 -> kein Fehler
  • beides mit u4 -> kein Fehler
Es lag also nicht am jdk sondern direkt an der Jvm bzw dessen Einsatz bei uns. Ich konnte das Problem dann lösen, indem wir überall die Variable JAVA_TOOLS_OPTS gesetzt hatten. Jetzt läuft es wieder. Export JAVA_TOOLS_OPTS='-Dfile.encoding=UTF-8'


Kategorie: Java --> Programmierung --> Computer

Neue Version von Morphium V2.0.24

Mi, 20. 11. 2013 - Tags:

Kleine Änderung:

Es gibt jetzt ein zusätzliches Setting in MorphiumConfig (checkForNew) welches festlegt, ob bei Objekten deren IDs nicht vom Typ object-id ist, durch zugriff auf die Datenbank geprüft werden soll, ob es sich um ein neues Dokument oder die änderung von einem bestehenden handelt. Andernfalls funktioniert @CreationTime nicht richtig. Außerdem kann dann die ID gesetzt werden, falls sie schon bekannt ist.

Wie üblich ist die neue Version hier in github , auf google code und in Maven central verfügbar.

 

Minor Changes:

There is an additional setting in MorphiumConfig, called checkForNew. If true, the database ist called for finding out, whether or not the object to store is new or not. This is necessary for making the @CreationTime annotation to work correctly. In addition to that, the ID can now be set, if not known already.

Asl usual, the new version as source and binary is available here on github, on google code and in Maven central.

 

 


Kategorie: Computer

MongoDB auf dedizierter Hardware

Fr, 15. 11. 2013 - Tags:

Bei holidayinsider.com haben wir als primäre Datenbank MongoDB im Einsatz. Das war nicht immer ganz einfach, es gab da schon so einige Probleme (ich habe darüber berichtet). Insbesondere hatten wir seltsame  Performanceprobleme, so dass es manchmal 75 Sekunden(!) gedauert hat, einen Datensatz von ein paar Byte zu lesen.

Und das, obwohl auf der Mongo selbst nicht viel passiert zu dieser Zeit. Aber natürlich gab es Last auf anderen VMs. Dennoch war das super nervig: ein Job auf VM10 läuft und mongo hängt kurz... das war nicht akzeptabel.

Das hatte noch andere Auswirkungen: Wenn wir die Datenbank komprimiert haben (d.h. Verzeichnis löschen und neu synchornisieren lassen) hat das meistens ca. 24h für knapp 100GB gedauert. Und während der Synchronisierung waren die Antwortzeiten der Mongo wirklich desolat - teilweise durchgängig keine Antwort in weniger als 5 Sekunden.

Das war nicht mehr tragbar. Ich habe in Morphium deswegen längere Timeouts und retries eingebaut, die ganze API asynchron gemacht, damit unsere Webanwendung nicht down geht.

Nach unzähligen versuchen, VMWare zu optimieren, sind wir doch zu dem Entschluss gekommen, dedizierte Hardware zu verwenden. Ich hatte schon damit gerechnet, ca. 48h mit synchronisieren beschäftigt zu sein...

Weit gefehlt. Das Synchronisieren der Knoten ging innerhalb von ca. 3h, und zwar alle 3 gleichzeitig. Vorher konnte man gar nicht meh als einen Knoten gleichzeitig synchronisieren, da wir sonst quasi down waren.

Nach dem die Synchronisierung beendet war hatten wir allein dadurch, dass wir migriert haben, die Antwortzeiten der Mongo halbiert:

2013-11-14_16-57-21

 

 

Und: jetzt nach den ersten 24h seit der Umstellung sind auch die seltsamen 75 Sekunden Peaks weg!

 

Tipp: MongoDB am besten nur auf dedizierter Hardware nutzen, nicht virutalisiert. Es sei denn, man hat Zeit....


Kategorie: Java --> Programmierung --> Computer

New Docu for Morphium V2.0.23

Do, 07. 11. 2013 - Tags:

Just added some new documentation for morphium at http://sboesebeck.github.io/morphium/

New Pages are:

And a lot more...

have a look at http://sboesebeck.github.io/morphium/ and the wiki pages at GitHub


Kategorie: Computer

Qnap als GIT-Server - SSH Probleme

Mi, 06. 11. 2013 - Tags: git qnap

Linux ist schon was tolles, insbesondere, weil man dadurch die Möglichkeit hat, den Funktionsumfang von linux basierten Geräten zu erweitern oder auch Unzulänglichkeiten auszugleichen / zu korrigieren.

Letzteres trifft auf die Qnap zu. Ich nutze für meine Softwareprojekte GIT - sowohl in der Firma (holidayinsider) als auch für meine privaten Projekte.

Das funktioniert als Versionskontrollsystem so, wie man es sich wünscht. Wer schon mal an CVS oder SVN verzweifelt ist, der sollte sich mal git ansehen. Ich habe meine git repositories auf einem Share auf meiner Qnap daheim, der Zugriff darauf geht dadurch recht leicht. Leider entwickle ich mit dem Laptop und diese Geräte zeichnen sich insbesondere dadurch aus, dass sie mobil sind und nicht immer in heimischen Gefilden genutzt werden. Was leider auch dazu führt, dass man mal nicht Zugriff auf das Laufwerk direkt hat. Deswegen ist es eher unpraktisch, das Laufwerk automatisch zu mounten (also beim Hochfahren oder einloggen). Das kann man zwar abhängig von Umgebungen machen aber das ist eine andere Geschichte... Ich habe gesehen, dass man auf der Qnap auch ein Paketverwaltungssystem hat, welches einem nahezu alle Linux-Tools nutzbar macht - unter anderem eben auch git. Die Installation war denkbar leicht, ein simpler Mausklick - und kurz warten. Einen Git-Server wollte ich so aber nicht einsetzen, sondern am ehesten über SSH das ganze machen - und SSH kann die qnap ja - eigentlich!

Denn jetzt kommen wir zur Krux - Es ist quasi unmöglich, dem SSHD der Qnap einzureden, dass man sich als normaler User anmelden kann. Man kann sich die sshd_config anschauen, so viel mal will - da steht alles drin, und dennoch geht's nicht. Auf der Oberfläche seht auch der Hinweis, dass man sich nur als admin einloggen kann.

Das ist dennoch komisch. Wenn man sich aber mal genauer den SSHD anguckt (z.B. mit strings oder hexdump) und ihn mit dem "offiziellen" sshd von Openssh vergleicht, stellt man schnell fest, dass die Jungs von QNnap sich das Ding einfach zurechtgeschnippelt  und eben den Support für andere User hard disabled haben. Ok, netter versuch QNap, aber ich bin root auf dem Ding - auch wenn er da nicht root heißt - lets hack. emoji people:smirk Die Lösung ist, über die Oberfläche jetzt schnell das Package für openssh installieren, dann bekommt man die offiziellen Binaries. Damit hat man schon mal die halbe Miete, ein Binary das zumindest das tut, was es soll. Das allein hilft allerdings leider auch nix, denn der qnap-sshd ist ja noch drauf und wird verwendet. Man könnte auch die Start-Skripten umschreiben oder ein eigenen Startup machen. Beides hat seine Vor- und Nachteile. Letzterer hat den Nachteil, dass man den SSHD nicht mehr über die Oberfläche steuern kann. Mein Ansatz:

  1. Original umbenennen, liegt in /usr/sbin: mv sshd sshd.qnap
  2. link auf den offiziellen SSHD anlgen, auch in /usr/sbin: ln -s /opt/sbin/sshd
  3. sshd neu starten - entweder über die Oberfläche (Häkchen an / Häkchen aus) oder mit : killall sshd; /usr/sbin/sshd -f /etc/ssh/sshd_config

das sollte es gewesen sein, jetzt kann ich mich selbst einloggen und den Pfad zum Repository angeben... Aber halt, wie ist das? Ich hatte ein eigenes Laufwerk dafür und für alle Laufwerke auf der Qnap existiert ein Link in /share... also wenn das Laufwerk "development" hieß, das git Repository in "git/repo" liegt, ist der Pfad auf der qnap also /share/development/git/repo. in Git gibt man einfach ein:

git clone user@qnap:/share/development/git/repo

Falls es einem schlaflose Nächte bereitet, dass der user sich auch einloggen kann auf der Qnap, der kann zusätzlich noch in der /etc/passwd die Loginshell für den entsprechenden user von /bin/sh auf /bin/git-shell umstellen - dann gibt es kein login mehr, nur noch git. Allerdings: die User settings sind dann "durcheinander". Die Files gehören dann diesem User, was evtl. etwas komisch sein könnte. Noch ein Nachtrag: beim nächsten Update sind die Änderungen natürlich wieder futsch, dann muss man evtl. von vorne beginnen. Also, falls ihr an eurer Qnap solche dinge gemacht habt, merkt euch, wie ihr das wieder hin bekommt, oder lasst die nächsten Updates einfach aus ;-)

Update 3/2014: Mit dem letzten Update der Qnap-Firmware ist die sache etwas komplizierter geworden. So kann man z.B. den SSHD nicht mehr neu starten, wenn man per SSH angemeldet ist. Ihr solltet das also über eine Telnetverbindung machen. Außerdem wird die /etc/ssh/sshd_config neu geschrieben. Wenn ihr da Änderungen gemacht habt, solltet ihr die evtl. nachtragen, sonst kommt ihr nicht mehr ran. Insbesondere die erlaubten User sind dann weg.

Happy hacking...


Kategorie: Java --> Programmierung --> Computer

New Version of Morphium Mongodb POJO Mapper V2.0.23

Mi, 06. 11. 2013 - Tags:

Minor features added:

  • support for java.util.Date types for created timestamps
  • support and test for expiring indices of mongo
  • minor fix for support of $natural sorting
 

take a closer look at: http://sboesebeck.github.io/morphium/


Kategorie: Computer --> Programmierung --> Objective-C

RSA Implementierung in Objective-C

Mo, 04. 11. 2013 - Tags: objective-c security encryption

Das war schwieriger als gedacht.

Ich hatte ja schon mal einige Erfolgsmeldungen bezüglich meines kleinen Hobbyprojektes hier verkündet, und doch steckt da der Teufel im Detail. Ich habe etliche Stunden in die Implementierung gesteckt – eigentlich ein dämliches Unterfangen, denn es gibt weit bessere. Aber so weit ich das gesehen habe, keine, die rein auf Objective-C basiert. Die Implementierung von iOS kapselt ja alles recht gut und doch funktioniert halt leider nicht alles bzw. ich bin zu blöd, da die richtigen Zertifikate etc anzulegen. Wäre auch ein wenig overkill für das, was ich machen will.

RSA und PKI – Unterschied?

Noch mal ne kurze Erklärung zu den Abkürzungen: RSA steht für Rivest, Shamir und Adleman. Das sind die Namen der Cryptographen, denen dieses Verschlüsselungsverfahren eingefallen ist. Naja… eingefallen klingt so zufällig, sie haben schon daran gearbeitet. Genaueres dazu gibt’s auch hier.

In gleichem Atemzug wird auch meistens PKI erwähnt, das steht für „Public Key Infrastrukture“ und bezeichnet Verfahren, Methoden und Vorgehensweisen, die es möglich machen mit Hilfe von RSA (oder ähnlichen Asymmetrischen Verschlüsselungsverfahren) die Sender zu identifizieren und sicher zu stellen, dass da keine „dazwischenfunkt“, wie man so schön sagt. Und in diesem Fall ist das fast wörtlich zu nehmen (Stichwort „Man in the Middle“).

Allerdings verursacht gerade diese PKI eine unheimliche verkomplizierung der Verschlüsselung: Zertifikate müssen erstellt werden, die von Zertifizierungsinstanzen zertifiziert werden müssen, welche selbst wiederum eine Zertifizierung von einer höheren instanz benötigen. Die Technik dahinter ist gar nicht so kompliziert, komplex wird’s eben durch dieses Drum herum.

Ich willl gleich mal festhalten, dass das alles wichtig und sinnvoll ist! Ohne PKI wäre das internet noch unsicherer, als es jetzt schon ist. Es gäbe kein SSL, kein HBCI und somit auch kein sicheres Online Banking. Also, ich finde das alles sehr sinnvoll…

Und dennoch gibt es fälle, in denen man einfach nur verschlüsseln will, ohne den ganzen Zinober drum rum. Bei Email würd ich mir das wünschen. Ich weiß ja jetzt auch nicht, wer am anderen Ende sitzt. Da versteh ich das ganze Getue nicht wirklich. Und DE-Mail ist ja eh nur – gelinde gesagt – Geldmacherei (um die man über kurz oder lang leider nicht rum kommen wird 🙁 ).

Anyway… ich wollte für mein kleines Hobbyprojekt einfach nur verschlüsselte Nachrichten von einem iPhone auf ein anderes senden. Da steckt der Teufel im Detail.

Die Implementierung

Ich hab ja hier schon ein paar Einblicke in die Implementierung des ganzen gemacht und seit dem ist dann doch ne menge Zeit vergangen. Den dort beschriebenen Ansatz habe ich beibehalten. Die Java-Sourcen von GNU-BigInteger haben mir dabei als Anhaltspunkt gedient.

Allerdings wollte ich, dass der Code sowohl auf OSX als auch auf gängigen iPhones funktioniert – die Java Version sollte eigentlich relativ problemlos auch auf Android gehen (hab ich aber (noch) nicht getestet). Das verursachte ungeahnte Probleme: die wirklich objektorientierte Implementierung mit NSMutableArray und NSNumber war leider viel zu langsam um nutzbar zu sein. Ich musste den code dann noch mal komplett umschreiben um dann mit Integern zu funktionieren. Auch da gab es Fallstricke: int auf iOS != int auf OSX. Die Bitlängen variieren da. Weshalb ich die interne Repräsentation der Daten komplett auf int64_t gebaut habe.

Wie in dem letzten Post schon beschrieben, blieb „nur noch“ die Umwandlung von Datenblöcken (NSData*) in BigInteger-Arrays übrig, die man dann verschlüsseln kann, bzw. umgekehrt entschlüsseln

Das erwies sich als erstaunlich kompliziert! Hier ein paar Anmerkungen:

  • Die Verschlüsselung funktioniert nur mit Zahlen, die kleiner sind als die Schlüsselbreite. Also, wenn ich RSA-1024 Bit habe, muss ich meine Daten auf <1024 Bit splitten.
  • Das Splitten klappt leider nicht immer aufs Byte genau auf die Grenzen von unseren BigIntegers. Deswegen muss man sich geeignete Mechanismen überlegen, um das ganze verlustfrei hin zu bekommen
  • Es müssen auch die Sonderfälle klappen, also 10 Byte 00, oder kein Byte…
  • Und das ganze muss reversibel bleiben, d.h premarin cream. ich muss aus den BigInteger-Objekten auch wieder meine Daten raus kriegen.

All das hat echt ne Menge Zeit gekostet. Ein wenig Code dafür könnt ihr hier sehen:

 

[codesyntax lang=“objc“ lines=“normal“]

- (NSArray *)getIntegersofBitLength:(int)bitLen {
////take the self, chunks of bitsize - 1
//    bitLen -= 32;
    int dataSize = (bitLen - 1) / 32; //bytes for this bitlength allowdd

    int numBis = self.length / dataSize / 4;
    if ((bitLen - 1) % 31 != 0) {
        numBis++;
    }
    int skip = 0;
    if (self.length % (dataSize * 4) != 0) {
        numBis++;
    }

    NSMutableArray *ret = [[NSMutableArray alloc] initWithCapacity:(NSUInteger) numBis];

    char *buffer = malloc(self.length);

    NSRange range = NSMakeRange(0, self.length);

    [self getBytes:buffer range:range];
    //creating numBis integers
    for (int loc = 0; loc < self.length; loc += (dataSize * 4)) {
        int numDatIdx = 0;

        range.location = (NSUInteger) loc;
        if (loc + dataSize * 4 > self.length) {
            range.length = self.length - loc;
            dataSize = range.length / 4;
            if (range.length % 4 != 0) {
                dataSize++;
            }
        } else {
            range.length = (NSUInteger) dataSize * 4;
        }

        int64_t *numDat = [BigInteger allocData:dataSize + 1];

        //prefixing all bis - to make 00000000 possible
        numDat[dataSize] = dataSize * 4; //prefix number of bytes
        numDatIdx = dataSize - 1;

//            NSLog(@"Got buffer %d, %d    %@", range.location, range.length, [[NSData dataWithBytes:(buffer + range.location) length:range.length] hexDump:NO]);

        for (int i = range.location; i < range.location + range.length; i += 4) {
            unsigned char c = (unsigned char) buffer[i];
//                NSLog(@"Processing idx %d-%d", i, i + 4);
            int v = c << 24;
            if (i + 1 >= range.location + range.length) {
                numDat[numDatIdx--] = v;
                skip = 24;
                break;
            }
            c = (unsigned char) buffer[i + 1];
            v |= c << 16;

            if (i + 2 >= range.location + range.length) {
                numDat[numDatIdx--] = v;
                skip = 16;
                break;
            }
            c = (unsigned char) buffer[i + 2];
            v |= c << 8;

            if (i + 3 >= range.location + range.length) {
//                    v=v>>8;
                numDat[numDatIdx--] = v;
                skip = 8;
                break;
            }
            c = (unsigned char) buffer[i + 3];
            v |= c;
            numDat[numDatIdx--] = v;
            skip = 0;
        }

        BigInteger *bi = [[BigInteger alloc] initWithData:numDat iVal:dataSize + 1];
        if (numDatIdx > -1) {
            //need to skip bytes
            numDatIdx += 1;
            int64_t *arr = (int64_t *) [BigInteger allocData:(int) (bi.iVal - numDatIdx)];
            memcpy(arr, bi.data + numDatIdx, bi.iVal - numDatIdx);
            bi.data = arr;
            bi.iVal = bi.iVal - numDatIdx;
        }
//            NSLog(@"Created BigInteger Ints : %@", bi);
//            NSLog(@"  bits: %d, DataSize %d", bi.bitLength,bi.iVal);
        [bi pack];
        [ret addObject:bi];
    }

//    }
    if (skip > 0) {
        BigInteger *bi = [ret lastObject];
        [ret removeLastObject];
        //remove prefix :/
        int64_t len = bi.data[bi.iVal - 1];
        len = len - skip / 8;
        bi.data[bi.iVal - 1] = 0;

        if (![bi isZero]) {
            bi = [bi shiftRight:skip];
//            bi=[bi or:[BigInteger valueOf:last]];
            [bi pack];
            int64_t *dat = [BigInteger allocData:(int) (bi.iVal + 1)];
            dat[bi.iVal] = len;

            for (int i = (int) (bi.iVal - 1); i >= 0; i--) {
                dat[i] = bi.data[i];
            }
            bi.data = dat;
            bi.iVal += 1;

            if (![bi isZero])
                [ret addObject:bi];
        }
    }
    return ret;
}

[/codesyntax]

 

 

da sind zwar noch ein paar Debug-Ausgaben drin, aber die helfen vielleicht beim Verständnis. Der Knackpunkt ist ziemlich am Anfang: Wie kann man die 0000000-Byte-Version abbilden. Ich meine, eine Zahl mit beliebig vielen führenden nullen ist 0. Deswegen habe ich in der Methode zum umwandeln von Bytes in BigIntegers die Länge in Bytes als Präfix mit eingefügt. Dadurch ist auch eine Menge von 00en nicht 0 – sondern z.B. 400000000 oder so… und diese Zahl kann man dann auch verschlüsseln.

Auf die gleiche Art und weise könnte man hier noch – der Sicherheit wegen – eine Prüfsumme einfügen. Wenn man denn will…

Leider geht das nicht als Ersatz für den Präfix „Länge in Byte“, da ich diesen Wert auch beim decodieren wieder benötige.

Im Normallfall klappt ja alles recht einfach, aber was, wenn die Daten eben nicht an den Grenzen zum BigInteger anfangen / aufhören? Dann muss man es am Ende noch mal „zurechtrücken“ damit das selbe raus kommt. Das erkennt man recht gut ab der Zeile „if (skip>0)“ – das ist genau der Fall, bei dem rotiert werden muss. Beispiel:

ich habe die Daten AFFE1 mein BigInteger hat eine ByteLänge von 64Bit, nach obigem Algorithmus käme dann raus: AFFE1000 was natürlich falsch wäre. Allerdings haben wir den Skip-Value, mit dem die Daten dann zurechtrotiert werden können (shiftRight).

Danach passt wieder alles.

[codesyntax lang=“objc“ lines=“normal“]

+ (NSData *)dataFromBigIntArray:(NSArray *)bigInts hasPrefix:(BOOL)prefix {
    char *buffer = malloc(sizeof(char) * 4); //4byte = 1int
    NSMutableData *ret = [[NSMutableData alloc] init];
    for (int i = 0; i < bigInts.count; i++) {
        BigInteger *integer = (BigInteger *) bigInts[i];

//        NSLog(@"processing %@",integer);
        buffer[0] = buffer[1] = buffer[2] = buffer[3] = 0;
        //stepping through integers
        int j = (int) (integer.iVal - (prefix ? 2 : 1));
        int skipBytes = 0;
        if (prefix) {
            //read prefix, add bytes if not enough in BigInteger
            int pr = (int) integer.data[integer.iVal - 1];
            if (pr < (integer.iVal - 1) * 4) {
                //too many 000
                skipBytes = (int) ((integer.iVal - 1) * 4 - pr);
            }
        }
        int skip = skipBytes;
        for (; j >= 0; j--) {
            int64_t v = integer.data[j];
            int idx = 0;
            char val;
            if (skip > 0) {
                skip--;
            } else {
                val= = (char) ((v >> 24) & 0xff);
                buffer[idx++] = val;
            }

            if (skip > 0) {
                skip--;
            } else {
                val = (char) ((v >> 16) & 0xff);
                buffer[idx++] = val;
            }

            if (skip > 0) {
                skip--;
            } else {
                val = (char) ((v >> 8) & 0xff);
                buffer[idx++] = val;
            }

            if (skip > 0) {
                skip--;
            } else {
                buffer[idx++] = val;
                val = (char) ((v) & 0xff);
            }

            if (skipBytes > 4) {
                skipBytes = skipBytes - 4;
            } else {
                [ret appendBytes:buffer length:(NSUInteger) (4 - skipBytes)];
                skipBytes = 0;
            }
        }

    }
    free(buffer);
    return ret;
}

[/codesyntax]

Das Decoden von so einer Liste von BigIntegers geht im Gegenzug wieder recht einfach, obwohl man auch hier wiederum diesen Offset berücksichtigen muss.

 

 

Die Auflösung eines BigInteger ist ja ein int64_t – also 64Bit = 8 Byte. Das Problem was es nun geben kann ist, dass wenn ein Wert < 2^64 abgebildet wird, kämen evtl. zu viele nullen ins Ergebnis. Also beispielsweise die Zahl FF.

Im BitInteger ist die Repräsentation ein int64_t mit dem Wert FF. Und ich muss jetzt wissen – ist das Ergebnis FF, 00FF oder 0000FF oder so; Bytegenauigkeit sollte hier eigentlich genügen, da die ausgangswerte ja auch Bytes sind. Und genau da kommt unser Präfix von oben ins Spiel. Er besagt, wie viele Bytes wirklich benötigt werden. Alle anderen werfe ich quasi weg, bzw. werden einfach übersprungen.

Damit kann man Blöcke von Bytes einfach in BigIntegers umwandeln und zurück. Und somit kann man diese BigInteger auch ver- bzw entschlüsseln.

Weiteres

die RSA Implementierung hab ich versucht als Library zu bauen, die sowohl ein Target für iOS als auch OSX hat. Ich habe auch einige Unit-Tests implementiert, die die Funktionen ziemlich weit abdecken. Das klappt auch so weit, allerdings habe ich es bisher nicht geschafft, ein iOS erfolgreich dagegen zu linken. Er findet immer einige Implementierungen nicht, was ich mir nicht wirklich erklären kann. Da ist also noch was zu tun.

Dann muss das ganze noch um Asynchronität erweitert werden, damit man in der Oberfläche eine Prozentanzeige darstellen kann, währen er ver- oder entschlüsselt.

Eine kleine Test-App mit der man das alles ausprobieren kann, läuft schon damit:


Kategorie: Apple --> Computer

von Java zu Objective-C

Do, 24. 10. 2013 - Tags:

Ich arbeite ja nun schon seit einigen Jahren mit Java und habe da so manches größeres und auch kleineres Projekt verwirklicht (einige OpenSource-Projekte wie Morphium (link2) und auch kommerzielles wie CalugaMed für Arztpraxen oder Physiotherapeuten oder natürlich holidayinsider.com). Ich hab auch schon einige Dinge für Palm gebaut - aber das war ein anderes Zeitalter.

Dennoch hat mich die Entwicklung auf iOS interessiert, seit das iPhone seinen Siegeszug durch die Mobiltelefonindustrie gestartet hat. Ich hatte nur nie wirklich die Zeit mich ausführlich mit Objective-C auseinander zu setzen.

Seit wir auch in der Firma anfangen, eigene Apps zu entwickeln, musste ich mir doch mal die Zeit nehmen und mir das ganze etwas genauer zu Gemüte führen.

Ich hab ja gerade angefangen, den RSA-Algorithmus auf Objective-C zu implementieren, und meine Erfahrungen möchte ich hier gerne mal zusammenstellen. Das hier soll aber nur ein paar Hinweise geben und vielleicht ein wenig Angst bei Neulingen abbauen ;-)

Außerdem vergleiche ich hier Java auf dem Desktop bzw. Java auf dem Server mit Objective-C auf dem Desktop (OSX) bzw. dem Handy. Eigentlich müsste man noch einen Vergleich zw. Java/Android und Java/iOS ziehen - der kommt vielleicht später ;-)

Es ist eindeutig nicht so, dass das hier ein Einführungskurs in Objective-C sein soll, noch ist das hier vollständig. Aber vielleicht zeigt es dem ein oder anderen einen Weg, wie man in das Coding für iOS einsteigen kann.

Ähnlichkeiten und Unterschiede Objective-C und Java

Beides ist objektorientiert. Man kann in Objective-C also genauso objektorientiert arbeiten, wie in Java. Es gibt einige Strukturen, die sind syntaktisch ziemlich ähnlich und helfen es einem, sich schnell "zuhause" zu fühlen. Also ein IF ist in Objective-C eigentlich gleich wie in Java... Schleifenkonstrukte sehen sich auch sehr ähnlich. Auch die Primitiven Datentypen sind gleich. Also zumindest mit einfachen Aufgaben sollte man da keine Probleme haben.

Strings werden in Objective-C allerdings schon anders geschrieben. Da muss man wissen, dass in C ein String in der form "ein String" eigentlich ein an dieser stelle eingefüges array von Bytes ist, das zufällig diesen String ergibt. ist dann in c normalerweise auch so was wie:

[codesyntax lang="c"]

char* string="Halo Welt";

[/codesyntax]

Das ist also KEIN Objekt oder eine Struktur oder sonst was - diese Zeichenketten sind C-Zeichenketten und müssen auch mit C-Funktionen bearbeitet werden (wie strlen, strcopy etc).

In Objective-C benötigt man aber Instanzen von Objekten, man möchte also eine  Instanz eines NSString.

Das geht durch anstellen eines @ vor das erste Anführungszeichen:

[codesyntax lang="objc"]

NSString *str=@"Hallo Welt";

int len=[str length];

[/codesyntax]

Nur wenn man ein Objekt bzw. die Instanz einer Klasse hat, kann man diesem auch Nachrichten senden (in Java Jargon "Methoden aufrufen"), in diesem Fall die Nachricht "length" - also gib mir die Länge von dem String.

Ein sehr praktisches Feature sind die Categories in Objective-C. Damit kann man quasi bestehende Klassen um Funktionalität erweitern, ohne sie Abzuleiten. Das ist wirklich praktisch und sehr mächtig. Macht den Code etwas übersichtlicher und man kann damit zusätzlich zur Vererbung arbeiten. Das ist auch ein Feature, dass in in Java gerne hätte....

Null-Pointer

Da unterscheiden sich die beiden Sprachen wirklich erheblich (ich beziehe mich hier auf Objective-C, nicht C/C++).

In Objective-C heißt der Null-Pointer (der in java ja bekanntlich null heitß): nil

Es gibt aber auch noch den guten alten Nullpointer von C namens NULL! Das sind aber verschiedene Dinge, bitte nicht verwechseln. Daumenregel: benutze ich OBjective-C API dann immer mit nil arbeiten, bei C-Aufrufen mit dem entsprechenden Pendant.

Ok, das wäre ja nicht so dermaßen seltsam, ABER: Alle Nachrichten, die an nil gesendet werden, liefern nil zurück (und tun natürlich nix)! Es gibt keinen Fehler zur Laufzeit! Nur einfach kein Ergebnis! Zum Vergleich: in Java gibt es da eine Nullpointer-Exception - einer der beliebtesten und weit verbreitetsten Fehler schlichthin ;-)

Das kann sehr nervig zu debuggen sein, und man sollte des öfteren mal ein "if (irgendwas==nil)" einfügen, um sicher zu sein.

Aber: man kann sich leider nicht immer drauf verlassen, dass nil einfach funktioniert und nix tut, einige Zugriffe klemmen dann doch und werfen Fehler (meist BAD ACCESS oder so): z.B. wenn man versucht, auf einem nil-Pointer mit Array-Index zuzugreifen -> Boing!

Das macht die Sache etwas komplizierter. Ich fand den Effekt am Anfang eigentlich ganz nett, Juhuu, nie Wieder Nullpointer-Exceptions. Zumindest erweckt das Konzept den Anschein. Das ist zwar voerdergründig richtig, verursacht aber beim Debuggen des Öfteren mal Kopfzerbrechen - "warum ist denn das NIL???? hä?"

Kleiner Tipp: falls nil Eingaben nicht erlaubt sind, prüft das entsprechend und schmeißt eine Exception.

Syntaxvergleich

mir persönlich liegt die Syntax von Java mehr, aber das mag auch daran liegen, dass ich mehr mit Java gemacht habe und vor allem kein amerikanisches Tastaturlayout verwende. Denn Objective-C ist gespickt mit eckigen Klammern.

hier mal ein Beispiel in Objective-C:

[codesyntax lang="objc"]

BigInteger *i = [[BigInteger alloc] init];
BigInteger *result= [i add:[BigInteger valueOf:@"AFFE" usingRadix:16]];
NSLog(@"this is the result %@",result);

[/codesyntax]

und jetzt der identische Code in Java

[codesyntax lang="java"]

BigInteger i=new BigInteger(); BitInteger result=i.add(BigInteger.valueOf("AFFE",16)); System.out.println("this is the result "+result);

[/codesyntax]

an den * kann man die nahe Verwandtschaft zu C/C++ erkennen. Für alle Nicht-Eingeweihten: * bedeutet so viel wie "Zeiger auf".

man erkennt, wie "geschwätzig" Objective-C sein kann. Das kann wirklich etwas nervig werden. Was dabei vor allem nervt, ist, dass die Parametertypen nicht mit in die Signatur aufgenommen werden. Definiert man in java zwei Methoden mit verschiedenen Parametern, werden die auch getrennt behandelt. In Objective-C wird nur der Name als Methodensignatur verwendet. (also im obigen Beispiel valueOf:usingRadix:). Da tippt man sich evtl. die Finger wund.

Auch meckert Objective-C nicht unbedingt laut genug an (meistens wird es nur als Warning angezeigt), wenn man den falschen Parameter übergibt - anstelle eines Zeigers z.B. ein "long". Das bedeutet dann in diesem Fall, er interpretiert meinen "long"-Value als Zeiger irgendwo in den Speicher, wo er natürlich nix zu suchen hat => Crash!

Dafür gibt es in Objective-C einige Features, die ich mir in Java so einige Male wünschen würde… z.B. Blocks:

[codesyntax lang="objc"]

dispatch_block_t block = (dispatch_block_t) ^{ //Do Something here… };

[/codesyntax]

Solche Blocks können dann als Funktionsparameter z.B. übergeben werden oder auch benutzt werden, um mit "Grand Central Dispatch" Dinge zu parallelisieren (Ein super praktisches Feature auf OSX und iOS).

In Java muss das Gleiche über Interfaces und Anonyme Klassen geregelt werden, was aber nicht ganz das selbe ist - funktioniert aber auch.

Leider ist der Aufruf der Blöcke nicht Objective-C konform, sondern eher wie C... also in dem Oberen Beispiel würde man das aufrufen mit block(); Das kann schon verwirrend sein.

Alles in Allem muss man sagen, dass die Syntax von Objective-C doch ganz schön gewöhnungsbedürftig ist und die "Geschwätzigkeit" ist nicht immer angenehm.

Speichermanagment

Java hat da eindeutig die Nase vorn. Das Prinzip des Garbage Collectors ist schon super und funktioniert nahezu problemlos auch bei speicherintensiven Aufgaben in Java. In Objective-C gibt es so was ähnliches, es wird ein "AutoReleasePool" verwendet. Im Endeffekt ist das das gleiche, wie der Garbage Collector in Java, funktioniert intern nur etwas anders. Diesen Autorelease-Pool gibt es seit iOS 6 auch endlich auf iPhones, vorher war das nur Mac-Programmen vorbehalten. Damit muss man sich nicht mehr um das Speichermanagement kümmern.

Da war es so, dass jede Instanz eines Objektes, die man erstellt, retained wird, d.h. der UsageCounter wird um eins erhöht. Braucht man die Instanz nicht mehr, reduziert man den Counter mit release. Jeder kann sich denken, dass das binnen kürzester Zeit zu echt Komplexen Problemen und Speicherlöchern führt… eine Methode liefert ein Objekt zurück, welches vorher von irgendwo gekommen ist. Wer retained, wer released? Weis man das immer?

Ein Problem ganz anderer Art ist das Speichermanagement von Nicht-Objective-C strukturen. Dieser "Autoreleasepool" oder auch das Retain/Release Usage-Counting funktioniert natürlich nur für Objecitve-C eigene Objekte. Das bedeutet, sobald man z.B. auf eine C/C++ Funktion zurückgreift, wird Objective-C's Autoreleasepool davon nix wissen (können). Da ist man wieder selbst gefragt, den Rambedarf zu reduzieren und einmal allokierten Speicher wieder freizugeben.

Coding Speed

wie schnell ist man denn so im Vergleich beim Coden…. Das hängt sehr stark davon ab, was man eigentlich tut. XCode ist eine echt super Entwicklungsumgebung und ist wirklich einfach zu benutzen. Auch wenn der Code Editor  so seine Schwächen hat (da empfehle ich mal einen Blick auf AppCode zu werfen). Für die "normalen" Apps, die ja sehr GUI-Intensiv sind, ist das super. Und um einiges Besser als die meisten GUI-Designer, die für Java verfügbar sind.

Ich bin natürlich auch in Java auch deshalb schneller, weil ich viel mehr Erfahrung dort habe. Ich kenne die nötigen Frameworks, Konzepte und Ideen um meine Probleme umzusetzen oder die Herangehensweisen einschätzen zu können. In Objective-C tu ich mich da noch etwas schwer, mit gerade mal ner Handvoll Apps Erfahrung. Dennoch viel mir die Einarbeitung bisher noch recht leicht, mit der Java-Erfahrung im Kreuz ist eigentlich nur die Syntax ein wenig ungewohnt...

Wenn ich lange Objective-C gecoded habe und dann wieder Java programmiere, kommt mir das komisch vor ;-)

Alles in Allem würde ich sagen ist man bei einigen Aufgaben mit Objecitve-C bzw. XCode deutlich schneller, bei anderen even in Java... das hält sich in Etwa die Waage.

ABER: sobald man anfängt Objective-C mit C/C++ zu mischen und sonstige "Schweinereien" zu machen, kann insbesondere das debugging ein Graus sein!

Anmerkung: Speed

da wir gerade beim Thema sind... ich war SEHR erstaunt, was ich über die Ausführungsgeschwindigkeit herausgefunden habe. Es geht hier dabei um das erstellen von 2048Bit RSA-Schlüsseln, sowohl in Objective-C als auch in Java. Weiter infos dazu findet ihr hier.

Ich habe bei der Implementierung den Code von Java nach Objective-C portiert (auch deswegen, weil ich die ganzen RSA-Implementierungen in c/c++ nicht kapiert hab ;-))

Der Algorithmus ist also identisch. Ich habe zunächst einen sehr objektorientierten Ansatz zur repräsentation der BigInteger-Objekte genutzt. Die Zahlenwerte werden in der Java-Implementierung als int-Array abgelegt. Meine erste Objective-C Implementierung nutzt ein NSMutableArray und NSNumber.

Das war nicht so clever. Der Code war verständlich, aber die Ausführungsgeschwindigkeit desolat! Objective C war in diesem Fall ca. 100x langsamer als Java. Beim identischen Algorithmus!

Das hat micht schon erstaunt... Ich habe dann die Implementierung noch mal korrigiert und anstelle von NSNumbers und NSMutableArrays auch int-Arrays verwendet. Dummerweise muss man sich dann komplett selbst um das Speichermanagement kümmern.

Damit konnte ich den Speed immerhin auf Faktor 3 erhöhen - Also die Objective-C Implementierung war "nur" 3x so langsam, wie die Java Implementierung!!

Dank dem Einbinden von GCD ist es jetzt "nur" noch 1,5 bis 2x so langsam wie Java - Aber java bleibt DEUTLICH flotter.

Dabei gilt noch was anzumerken: der Erste Durchlauf in der Schleife zum Erstellen einer Primzahl in Java ist um ca. Faktor 60 Langsamer als in Objective-C... das ändert sich aber rasch, wenn der Just-In-time-compiler eingreift und dann auch noch den Code optimiert. Danach landet man auf ca. 2x schneller.

Ich bin nicht der Meinung, das Objective-C langsamer ist als Java... allerdings sollte man sich beim Coden doch seine Gedanken über Performance machen. Offensichtlich gibt es beim Coden mit Objective-C so einige "Fallstricke" und "Bremsen" auf die man achten muss. Und die Algorithmen müssen vermutlich noch mal genauer auf Performance untersucht werden. Ich weiß nicht, in wie weit es Optimierungspotential in den Einstellungen gibt, aber so gravierend werden die sich vermutlich nicht auswirken.

Nachtrag dazu: Was beim Arbeiten mit Objective-C ganz besonders positiv auffällt ist, die Startup-Time! Also, nachdem man etwas kompiliert hat, bis es dann wirklich läuft (mal abgesehen vom iOS-Simulator). Aber da ist Objective-C bestimmt 10x schneller als Java!

Anmerkung: Platformunabhängikeit

Das war auch so ein Ding... Ich hab die RSA und BigInteger implementeriung erst für OSX gemacht - sollte ja kompatibel (zumindest weitgehend) zu iOS sein... als der code unter OSX endlich lief, hab ich das ganze versucht auf iOS zu starten... CRASH!

Warum: in iOS ist ein int 32 Bit Breit, in OSX 64! Deswegen musste ich den ganzen code noch mal anpassen unter verwendung von int32_t und int64_t... das hatte auch noch mal ein paar Fehler zu Tage gebracht, aber jetzt ist's auch in iOS lauffähig.

Also: achtet auf eure Datentypen! Die sind nicht immer und überall gleich (alte C-Weisheit, die auch auf Objective-C zutrifft).

 

Fazit:

Kann man überhaupt ein Fazit ziehen? Hier vergleicht man nun wirklich Äpfel mit Birnen. Und beides ist irgendwo gut, beides hat so seine Macken. Spass macht beides, bei beidem muss man lernen. Insbesondere die zugrunde liegende Laufzeitumgebung macht noch eine Menge aus und da finde ich Java im Momemnt etwas "aufgeräumter" - auch was Dokumentation und so betrifft.

Aber ich denke, jemand der zumindest schon mal mit objektorientierten Programmiersprachen gearbeitet hat und am besten schon mal C-Code gesehen hat, der tut sich mit Objective-C anfangs recht leicht - bei komplexeren Dingen wird die Sache in jeder Programmiersprache haarig.

 

 


Kategorie: Java --> Programmierung --> Computer

Morphium: Minor feature release avaliable V2.0.22

Mi, 16. 10. 2013 - Tags: java-2 morphium-2 programming

Gerade habe ich eine neue Version V2.0.22 von Morphium hochgeladen. Morphium ist ein object mapper und eine Abstractionlayer für den Zugriff von Java aus auf Mongodb.

Das aktuelle Release beinhaltet nur ein paar kleine Änderungen. Diese umfassen vor allem das Profiling und die Möglichkeit, im ProfilingListener zu erfahren, welcher Server geantwortet hat. Das macht natürlich nur in einem ReplicaSet und/oder sharded Environment Sinn.

Damit kann man aber eventuelle Performanceprobleme auf einzelnen Mongoknoten identifizieren und hoffentlich auch beheben... ;-)

Die Version ist auch im in Sonatype Nexus hochgelade und sollte bald auch über das Maven repository verfügbar sein. Bis da hin, kann man sich die aktuelle Version auch hier runterladen.


I just uploaded a new version (V2.0.22) of our caching object mapper and abstraction layer for mongodb called Morphium . This contains some minor changes regarding the profiling. Now the query object stores the information, which server answered the certain request. This is useful only in replicated or sharded environments and is especially helpful when tracking down performance issues on certain nodes.

The version is available on Sonatype Nexus and hence available via Maven soon. Until then, you can download the latest release here.


Kategorie: Allgemeines

Morphium - MongoDb Object Mapper now on GitHub

Di, 15. 10. 2013 - Tags:

As google will soon stop support for download of releases, we decided to move Morphium to GitHub. It's now available here: http://sboesebeck.github.io/morphium/ and   https://github.com/sboesebeck/morphium

For those of you, not familiar with Morphium, it's a Object Mapper and Abstraction Layer for MongoDB - it does support a lot of features, currently used in production for example at de.holidayinsider.com

have a look at the Wiki of Morphium for more information.


Kategorie: Computer --> Programmierung --> Objective-C

Verschlüsselung auf iOS mit Objective-C

Mi, 02. 10. 2013 - Tags: ios objective-c-2 rsa

Im Zuge der ganzen NSA/Prism/Snowden Diskussion und auch weil es mich interessiert, hab ich mich mal rangesetzt und mir ein paar Gedanken zum Thema "privacy & Security" im Internet gemacht.

Was momentan fehlt ist ein Weg, wirklich sicher zu kommunizieren. Die DE-Mail ist ja nur ein besserer Witz (sicherheitstechnisch zumindest) und GPG / PGP ist viel zu kompliziert zu benutzen und vor allem - man benötigt eine Identitätsprüfung, "komische" Zertifikate und anderes, was der Otto-Normal-User so nicht wirklich versteht.

Das ist alles viel zu weit davon entfernt, wirklich brauchbar zu sein oder auch nur annähernd Email den Rang abzulaufen. Eigentlich müsste es vollkommen transparent laufen und alles Verschlüsselt werden - von End-To-End. D.h. von einem Client werden die Daten so verschlüsselt, dass nur der Empfänger diese Daten entschlüsseln kann.

Im Zuge dessen habe ich versucht, auf IOS eine Public-Key-Verschlüsselung hin zu bekommen, um zu sehen, was man damit noch so anstellen kann. Das ganze hat mich doch vor mehr Probleme gestellt, als gedacht.

Zunächst mal ein Symmetrisches Verschlüsselungsverfahren - auch wenn das für das Ziel einer End-To-End-Verschlüsselung nicht wirklich ausreichend ist (symmetrische Schlüssel kann man immer per Brute-Force angreifen), so ist es doch wichtig, dass die Keys nicht irgendwo unverschlüsselt rumliegen, sondern, wenn überhaupt, dann mit einem guten Passwort verschlüsselt werden.

AES in Objective-C

Das war erstaunlicherweise gar nicht so kompliziert, es gibt da wirklich eine Menge Beispiele im Netzt zu, die auch wirklich funktionieren. Hier der Code zum Verschlüsseln von NSData:

[codesyntax lang="objc"]

- (NSData *)AES256EncryptWithKey:(NSString *)key{
    // 'key' should be 32 bytes for AES256, will be null-padded otherwise
    char keyPtr[kCCKeySizeAES256 + 1]; // room for terminator (unused)
    bzero( keyPtr, sizeof( keyPtr ) ); // fill with zeroes (for padding)

    // fetch key data
    [key getCString:keyPtr maxLength:sizeof( keyPtr ) encoding:NSUTF8StringEncoding];

    NSUInteger dataLength = [self length];

    //See the doc: For block ciphers, the output size will always be less than or
    //equal to the input size plus the size of one block.
    //That's why we need to add the size of one block here
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc( bufferSize );

    size_t numBytesEncrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt( kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                          keyPtr, kCCKeySizeAES256,
                                          NULL /* initialization vector (optional) */,
                                          [self bytes], dataLength, /* input */
                                          buffer, bufferSize, /* output */
                                          &numBytesEncrypted );
    if( cryptStatus == kCCSuccess )
    {
        //the returned NSData takes ownership of the buffer and will free it on deallocation
        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
    }

    free( buffer ); //free the buffer
    return nil;
}

[/codesyntax]

Das funktioniert tadellos und ist einfach zu verwenden. Da man symmetrisch verschlüsselt, muss man auch nicht mit 2 Keys rumhantieren und die evtl. sogar im KeyStore ablegen.

Die Entschlüsselung ist auch recht simpel:

[codesyntax lang="objc"]

- (NSData *)AES256DecryptWithKey:(NSString *)key{

    // 'key' should be 32 bytes for AES256, will be null-padded otherwise
    char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
    bzero( keyPtr, sizeof( keyPtr ) ); // fill with zeroes (for padding)

    // fetch key data
    [key getCString:keyPtr maxLength:sizeof( keyPtr ) encoding:NSUTF8StringEncoding];

    NSUInteger dataLength = [self length];

    //See the doc: For block ciphers, the output size will always be less than or
    //equal to the input size plus the size of one block.
    //That's why we need to add the size of one block here
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc( bufferSize );

    size_t numBytesDecrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt( kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                          keyPtr, kCCKeySizeAES256,
                                          NULL /* initialization vector (optional) */,
                                          [self bytes], dataLength, /* input */
                                          buffer, bufferSize, /* output */
                                          &numBytesDecrypted );

    if( cryptStatus == kCCSuccess )
    {
        NSLog(@"Decrypt success");
        //the returned NSData takes ownership of the buffer and will free it on deallocation
        return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
    }

    free( buffer ); //free the buffer
    return nil;
}

[/codesyntax]

RSA Implementierung

Standardweg mit Bordmitteln

Zunächst hab ich mal die Bordmittel von iOS 6 (und später 7) benutzt, um daten Verschlüsseln zu können. Das hat zunächst auch recht gut funktioniert - hier ein code-Beispiel:

[codesyntax lang="objc" lines="fancy" lines_start="1"]

NSData *wrappedSymmetricKey = data;
SecKeyRef key = yes ? self.publicKeyRef : self.privateKeyRef;

size_t cipherBufferSize = SecKeyGetBlockSize(key);
size_t keyBufferSize = [wrappedSymmetricKey length];

NSMutableData *bits = [NSMutableData dataWithLength:keyBufferSize];
OSStatus sanityCheck = SecKeyDecrypt(key,kSecPaddingPKCS1,
(const uint8_t *) [wrappedSymmetricKey bytes],cipherBufferSize,[bits mutableBytes],&keyBufferSize);
NSAssert(sanityCheck == noErr, @"Error decrypting, OSStatus == %ld.", sanityCheck);

[bits setLength:keyBufferSize];

return bits;

[/codesyntax]

 

Das ist der Code, der einen gegebenen Datenblock (NSData*) verschlüsselt und die verschlüsselten Daten wiederum als NSData zurückgibt. (die Basis für diesen Code findet sich hier). Das funktioniert so weit auch wunderbar.... solange man bei jedem Start der Application, das Schlüsselpaar neu generiert.

Aber eins nach dem anderen. Was macht der Code? Er nutzt die auch unter OSX bekannte "Schlüsselbundverwaltung" bzw. dessen Pendant von iOS. Dort müssen die Schlüsselpaare abgelegt werden um sie entsprechend nutzen zu können.

Die Erstellung und speicherung eines Schlüsselpaares geht z.B. so (auch wieder von hier):

[codesyntax lang="objc"]

OSStatus sanityCheck = noErr;
    publicKeyRef = NULL;
    privateKeyRef = NULL;

    // First delete current keys.
    [self deleteAsymmetricKeys];

    // Container dictionaries.
    NSMutableDictionary *privateKeyAttr = [NSMutableDictionary dictionaryWithCapacity:0];
    NSMutableDictionary *publicKeyAttr = [NSMutableDictionary dictionaryWithCapacity:0];
    NSMutableDictionary *keyPairAttr = [NSMutableDictionary dictionaryWithCapacity:0];

    // Set top level dictionary for the keypair.
    [keyPairAttr setObject:(__bridge id) kSecAttrKeyTypeRSA forKey:(__bridge id) kSecAttrKeyType];
    [keyPairAttr setObject:[NSNumber numberWithUnsignedInteger:kSecAttrKeySizeInBitsLength] forKey:(__bridge id) kSecAttrKeySizeInBits];

    // Set the private key dictionary.
    [privateKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id) kSecAttrIsPermanent];
    [privateKeyAttr setObject:privateTag forKey:(__bridge id) kSecAttrApplicationTag];
    // See SecKey.h to set other flag values.

    // Set the public key dictionary.
    [publicKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id) kSecAttrIsPermanent];
    [publicKeyAttr setObject:publicTag forKey:(__bridge id) kSecAttrApplicationTag];
    // See SecKey.h to set other flag values.

    // Set attributes to top level dictionary.
    [keyPairAttr setObject:privateKeyAttr forKey:(__bridge id) kSecPrivateKeyAttrs];
    [keyPairAttr setObject:publicKeyAttr forKey:(__bridge id) kSecPublicKeyAttrs];

    // SecKeyGeneratePair returns the SecKeyRefs just for educational purposes.
    sanityCheck = SecKeyGeneratePair((__bridge CFDictionaryRef) keyPairAttr, &publicKeyRef, &privateKeyRef);

    if (publicKeyRef == NULL) {
        NSLog(@"did not get keys");
    }
    LOGGING_FACILITY( sanityCheck == noErr && publicKeyRef != NULL && privateKeyRef != NULL, @"Something really bad went wrong with generating the key pair." );

[/codesyntax]

 

Dabei wird ein neues Schlüsselpaar generiert und die Referenzen darauf lokal abgelegt (Variablen publicKeyRef und privateKeyRef).

So weit so gut, das klappt wunderbar, hat nur einige Einschränkungen: so kann man keine Schlüssel erzeugen, die größer sind als 4096 bit, außerdem müssen an stelle von dem Schlüssel selbst ja auch noch einige Zusatzinformationen abgespeichert werden, wie z.B. unter welchem "Namen" die keys abgelegt werden etc.

Das, was mich am meisten daran gestört hat, ist aber, dass es eine sehr unhandliche Schnittstelle ist, wo zwischen Objective-C und C/C++ hin und hergemapped werden muss. (Anm. ich hab die letzten Jahr(zehnt)e hauptsächlich Java programmiert, da ist alles etwas "sauberer"). Eine schönere Objective-C implementierung hab ich nicht gefunden. (falls ihr eine kennt, immer her damit...)

Aber was mich wirklich genervt hat, ist, dass ich es ums verrecken nicht hin bekommen habe, diese dämlichen RSA-Keys z.B. aus den UserDefaults wieder zu lesen. No chance... man kann sich die Bits zwar holen, kann aus den Bits auch wieder Keys erstellen lassen... allerdings landen die dann irgendwo im Storage und ich bekomme den Key nicht mehr da raus. Entweder er ist nil oder er kann nicht zum entschlüsseln benutzt werden.

Also, hier der letzte Code-Stand, evtl. habt ihr ja ne Idee, was da klemmen kann:

[codesyntax lang="objc"]

 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSData *privKey = [defaults dataForKey:@"privateKey"];
    NSData *pubKey = [defaults dataForKey:@"publicKey"];
    RSA *rsa = [RSA shareInstance];
    [rsa deleteAsymmetricKeys];
    if (privKey == nil) {
        NSLog(@"No key stored");

        NSLog(@"Generating new keys... please wait");
        self.aesPwdTF.text = @"generating keys...please wait";
        [self.textTF setEnabled:false];
        [self.aesPwdTF setEnabled:false];
        [rsa generateKeyPairRSACompleteBlock:^{
            NSLog(@"Keys prepared");
            self.aesPwdTF.text = @"";
            [self.textTF setEnabled:true];
            [self.aesPwdTF setEnabled:true];
            [self.aesPwdTF becomeFirstResponder];
            [defaults setObject:[rsa privateKeyBits] forKey:@"privateKey"];
            [defaults setObject:[rsa publicKeyBits] forKey:@"publicKey"];
        }];
    } else {
        [rsa setPrivateKey:privKey];
        [rsa setPublicKey:pubKey];
        self.publicKeyTF.text = [[rsa publicKeyBits] base64EncodedString];
        NSLog(@"Private key %@", [[rsa privateKeyBits] base64EncodedString]);
        NSLog(@"read keys from defaults");
    }

[/codesyntax]

 

Das ist der Teil, der entscheided, ob der Key noch mal eingelesen werden soll. Klar, man könnte das auch im Keystore drin lassen, aber leider kann ich die daten ja nicht auslesen, denn hier klemmt es jedes Mal in der einen oder anderen Form:

[codesyntax lang="objc"]

- (void)setPrivateKey:(NSData *)privateKey {
    privateKeyRef = NULL;
    OSStatus sanityCheck = noErr;
    SecKeyRef peerKeyRef = NULL;

    LOGGING_FACILITY( privateKey != nil, @"Private key parameter is nil." );

    NSMutableDictionary *peerPrivateKeyAttr = [[NSMutableDictionary alloc] init];

    [peerPrivateKeyAttr setObject:(__bridge id) kSecClassKey forKey:(__bridge id) kSecClass];
    [peerPrivateKeyAttr setObject:(__bridge id) kSecAttrKeyTypeRSA forKey:(__bridge id) kSecAttrKeyType];
    [peerPrivateKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecAttrIsPermanent];
    [peerPrivateKeyAttr setObject:privateTag forKey:(__bridge id) kSecAttrApplicationTag];
    [peerPrivateKeyAttr setObject:privateKey forKey:(__bridge id) kSecValueData];
    [peerPrivateKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id) kSecReturnPersistentRef];

    sanityCheck = SecItemAdd((__bridge CFDictionaryRef) peerPrivateKeyAttr, (CFTypeRef *) &peerKeyRef);
    if (sanityCheck) {
        if (sanityCheck != errSecDuplicateItem)
            return;
        // Already have a key with this digest, so look it up to get its ref:
        [peerPrivateKeyAttr removeObjectForKey: (__bridge id)kSecValueData];
        [peerPrivateKeyAttr setObject: privateTag forKey: (__bridge id)kSecAttrApplicationLabel];//??
        [peerPrivateKeyAttr removeObjectForKey: (__bridge id)kSecReturnPersistentRef];
        [peerPrivateKeyAttr setObject: (__bridge id)kCFBooleanTrue forKey: (__bridge id)kSecReturnPersistentRef];
        SecItemCopyMatching((__bridge CFDictionaryRef)peerPrivateKeyAttr, (CFTypeRef*)peerKeyRef);
    }

    NSMutableDictionary *queryPrivateKey = [[NSMutableDictionary alloc] init];

    // Set the private key query dictionary.
    [queryPrivateKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
    [queryPrivateKey setObject:privateTag forKey:(__bridge id)kSecAttrApplicationTag];
    [queryPrivateKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
    [queryPrivateKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef];
    SecItemCopyMatching
    ((__bridge CFDictionaryRef)queryPrivateKey, (CFTypeRef *)&peerKeyRef);
    privateKeyRef = peerKeyRef;

}

[/codesyntax]

Das blöde ist, dass nach der Ausführung dieses Codes, sowohl im Simulator als auch auf dem Gerät die Variable privateKeyRef entweder nil ist, oder nicht für die Entschlüsselung verwendet werden kann.

Aber ich wollte ja eh eine "schönere" Schnittstelle bauen, die das ganze etwas entmystifiziert...

You don't own it, 'till you make it

Also, wie funktioniert eigentlich RSA. So kompliziert ist das eigentlich gar nicht, allerdings handelt es sich dabei um sehr große Primzahlen. Hier eine Beispielimplementierung in Java:

[codesyntax lang="objc"]

public class RSA {
   private final static BigInteger one      = new BigInteger("1");
   private final static SecureRandom random = new SecureRandom();

   private BigInteger privateKey;
   private BigInteger publicKey;
   private BigInteger modulus;

   // generate an N-bit (roughly) public and private key
   RSA(int N) {
      BigInteger p = BigInteger.probablePrime(N/2, random);
      BigInteger q = BigInteger.probablePrime(N/2, random);
      BigInteger phi = (p.subtract(one)).multiply(q.subtract(one));

      modulus    = p.multiply(q);
      publicKey  = new BigInteger("65537");     // common value in practice = 2^16 + 1
      privateKey = publicKey.modInverse(phi);
   }

   BigInteger encrypt(BigInteger message) {
      return message.modPow(publicKey, modulus);
   }

   BigInteger decrypt(BigInteger encrypted) {
      return encrypted.modPow(privateKey, modulus);
   }

   public String toString() {
      String s = "";
      s += "public  = " + publicKey  + "n";
      s += "private = " + privateKey + "n";
      s += "modulus = " + modulus;
      return s;
   }

   public static void main(String[] args) {
      int N = Integer.parseInt(args[0]);
      RSA key = new RSA(N);
      System.out.println(key);

      // create random message, encrypt and decrypt
      BigInteger message = new BigInteger(N-1, random);

      //// create message by converting string to integer
      // String s = "test";
      // byte[] bytes = s.getBytes();
      // BigInteger message = new BigInteger(s);

      BigInteger encrypt = key.encrypt(message);
      BigInteger decrypt = key.decrypt(encrypt);
      System.out.println("message   = " + message);
      System.out.println("encrpyted = " + encrypt);
      System.out.println("decrypted = " + decrypt);
   }
}

[/codesyntax]

(Gefunden hier)

Eigentlich ist es ja nur nötig, ein paar Primzahlen zu generieren, die man dann potenziert und den modulo berechnet, sowohl zum Ver- als auch Entschlüsseln.

Klingt eigentlich total simpel, aber die Herausforderung liegt darin, mit Zahlen zu rechnen, die mehrere 1000 bit lang sind - man muss also ne eigene Arithmetik abbilden.

Das kann beliebig komplex werden, weshalb ich mich bei meiner Implementierung an die GNU-Java BigInteger Implementierung gehalten habe und diese mehr oder minder auf Objective-C portiert habe (Source Code dafür ist hier).

Die Portierung ist nicht wirklich einfach gewesen, vor allem weil Java und Objective-C / C++ eine andere Vorstellung von primitiven Datentypen haben, aber zumindest läuft die BigInteger Arithmetik erst mal so weit.

Nachdem ich eine BigInteger Implementierung in Objective-C zur Verfügung hatte, ist die Verschlüsselung ähnlich simpel wie in java:

[codesyntax lang="objc"]

- (BigInteger *)encryptBigInteger:(BigInteger *)message {
    if (message.bitLength > self.bitLen) {
        NSLog(@"Encrypting impossible: Message is too long for key! Key is %d bits wide, message is %d", self.bitLen, message.bitLength);
        //splitting it? how?
        return nil;
    }
    return [message modPow:self.e modulo:self.n];
}

- (BigInteger *)decryptBigInteger:(BigInteger *)message {
    return [message modPow:self.d modulo:self.n];
}

[/codesyntax]

Wobei da sicherlich noch ein paar Optimierungen passieren könnten, wie z.B. das Splitten zu großer Eingaben in mehrere BigIntegers mit der richtigen Größe für die Verschlüsselung.

Ich denke, das Prinzip von RSA ist bekannt und kann so Anwendung finden. Ich werde hier sicherlich noch mehr Code posten der euch dann vielleicht auch ne Menge Arbeit erspart.

Was jetzt noch fehlt sind mehr oder minder 2 Dinge: Das umwandeln von NSData in eine Menge von BigIntegers sowie der zugehörige umgekehrte Weg. Wenn das funktioniert, kann man jeden BigInteger-Wert einzeln ver- bzw. entschlüsseln und kann somit beliebige Daten sicher übertragen oder ablegen.

Aber darüber ein andern mal mehr...

Warum das ganze?

Naja, zum einen hat es mich stark interessiert, mal so ein verschlüsselungsverfahren umzusetzen - und da ich in Objective-C keine passende Implementierung gefunden habe (einen Haufen Implementierungen in c/c++ ja, aber keine wirklich objektorientiert, die man als Java-Entwickler leichter versteht), lag es doch besonders Nahe, das gleich mal in Objective-C zu implementieren.

Eine Anwendung dafür schwebt mir auch schon vor, mal sehen, in wie weit dass dann wirklich seinen Weg in den App-Store findet... so "stay tuned" ;-)

 


Kategorie: Linux/Unix --> Computer

MongoDB Erfahrungen / Tips und Tricks

Mo, 17. 06. 2013 - Tags:

Noch ein paar Erfahrungen zum Thema mongodb... In den letzten Tagen habe ich wieder viel gelernt - teilweise mehr, als mir lieb sein kann...

Auf unserer Webseite de.holidayinsider.com nutzen wir MongoDB als primäres Data-Storage (wir haben PostgreSQL abgelöst). Wir waren schon kurz davor, die Entscheidung zu bereuen... aber eines nach dem anderen.

Installation

Die Installation von MongoDB ist denkbar leicht - einfach ein executeable ausführen und der Daemon läuft. Selbst als ReplicaSet (bzw. Cluster) ist MongoDB wirklich easy im Setup. Viel Logik, die sonst durch eigene Server gelöst wird, ist bei MongoDB in den Treiber gewandert. Der Treiber findet z.B. die aktiven Knoten raus und sendet Anfragen, je nach Konfiguration, an einen Sekundären oder Primären Knoten im Cluster.

Um das ganze automatisch beim Systemstart mit zu starten, muss man sich ein init-Script schreiben. Oder man verwendet die Pakete für die eigene distribution / das Betriebssystem der Wahl. Eventuell sind da auch schon startup-Skripte mit dabei. Wir haben alle mongod Parameter mit in das init-Script gepackt, man kann aber auch ein conf-file anlegen. Je nach gusto.

Die Firewalls sollten zwei Ports auf die MongoD zulassen - falls man den default nicht ändert: Port 27017 für den "binären" mongodb zugriff. Diesen Port nutzen die Treiber und die shell. Und Port 28017 (immer den Binär-Port +1000) für den HTTP-Zugriff (sofern man das möchte).

wir starten die Mongodb-Daemons folgendermaßen:

$MONGOHOME/bin/mongod --oplogSize $OPLOGSZ --rest --replSet REPL
  --pidfilepath /var/run/mongodb.pid --fork --cpu
  --dbpath $DATA --journal --logpath $LOG --logappend

Natürlich alles in einer Zeile. Kurze Erklärung zu den einzelnen Optionen:

--oplogsize: Damit legt man fest, wie viel Platz für das Operations-Log bereit gestellt werden soll. Dieses OpLog ist für die Replication wichtig und legt fest, wie weit dieser knoten hinter dem Rest "herhinken" darf.

--rest: Schaltet die Rest-API und den Zugriff via Webbrowser auf port 28017 ein.

--replSet REPL: spezifiziert den Namen des replicaset (s.u.)

--pidfilepath PATH: das file beinhaltet die PID des mongod nach dem Start

--fork: in den Hintergrund wechseln

--cpu: CPU Statistiken ins log schreiben

--dbpath: wo liegen die Daten

--journal: damit wird journalling eingeschaltet. Sollte man eigentlich immer anmachen, es sei denn, man benötigt noch mehr Performance

--logpath: wo liegt das logfile

--logappend: und hängen wir hinten an, oder wird überschrieben

Replicaset, ja oder nein? Sharding? oder was?

Im Zweifel: Ja! Denn sonst hat man mindestens eine Downtime, wenn man die Mongodatenverzeichnisse komprimieren will. Selbst wenn man keine zweite Maschine hat, ist man sogar noch besser dran, wenn man alles auf eine Maschine packt - das ist zwar nicht ausfallsicher, aber man hat eben den Vorteil, den ein oder anderen Knoten mal runter zu fahren und sich neu synchronisieren zu lassen.

Sharding ist auch so ein tolles Feature: im endeffekt werden dann die Daten auf grund von dem sog. Shard Key auf verschiedene Knoten verteilt. So könnte man z.B. die Daten auf Grund eine Timestamp verteilen. Oder abhängig vom Namen einer Person... oder oder oder.

An sich ne tolle Idee, so die last zu verteilen. (ihr hört vermutlich schon das ABER kommen, also: )

Aber leider ist das nicht immer sinnvoll. Denn z.B. auf Grund von einem Timestamp die Daten verteilen ist nicht unbedingt sinnig, da immer Chunks von 64 angelegt werden - d.h. es werden nicht die einzelnen requests verteilt. Außerdem müssen z.B. BulkInserts im Nachhinein evtl. noch "korrigiert" werden, d.h. die neu eingefügten Daten müssen evtl. auf andere Knoten verteilt werden.

Das hat bei uns zu Problemen geführt, die ich mittlerweile allerdings auf unser unzureichendes Storage und einige Konfigurationsfehler in den VMs zurückführe.

Einen kleinen Haken gibt es da auch noch: Das Setup wird ungleich komplizierter. Denn auch die Shards müssten eigentlich repliziert werden. D.h. die Daten müssten irgendwie redundant gehalten werden - 1. wegen der Ausfallsicherheit und 2. wg. dem oben genannten Komprimieren der Datenbank. Dann: einen Shard runter fahren alleine führt zu echt seltsamem Verhalten...

Wie man ein Replicaset aufsetzt kann man am besten auf der mongodb Seite nachlesen. Aber im Grunde sind nur folgende Dinge zu tun:

  • startet den Server mit der option -replset NAME, wobei NAME natürlich dem Namen in eurem Replicaset entspricht. Der muss auf allen Knoten des selben Sets gleich sein.
  • startet alle knoten im Replicaset
  • verbindet sich mit einem der Knoten mit der shell (mongo mongoserver/testdb). Dann erstellt die replicaset-Konfiguration in Java-Script. Das sieht in etwa so aus:
var config={
 "_id" : "REPLSETNAME",
 "version" : 38,
 "members" : [
 {
 "_id" : 0,
 "host" : "node1:27017",
 "priority" : 5
 },
 {
 "_id" : 1,
 "host" : "node2:27017",
 "priority" : 3
 },
 {
 "_id" : 10,
 "host" : "mongohidden:27017",
 "priority" : 0,
 "hidden" : true
 },
 {
 "_id" : 11,
 "host" : "arbiter:27017",
 "arbiterOnly" : true
 }
 ]
}
  • Natürlich müsst ihr die Rechnernamen und den Replicasetnamen korrigieren... in dem Beispiel ist auch ein Arbiter mit drin, der so gar nicht nötig wäre.
  • mit rs.initiate(config) wird das Replicaset initialisiert - voilá

Beim Aufsetzen eines Replicaset solltet ihr auf folgendes achten:

  • ungerade Anzahl an Knoten - falls das nicht geht, installiert einen sog. Arbiter. Das ist ein Mini-Mongodb-Daemon, der nur bei Wahlen zum Master mit macht. Da sich kein Knoten selbst wählen darf (er könnte ja auf Grund eines Netzproblems isoliert sein), gäbe es bei 2 Knoten sonst ein Problem, wenn einer ausfällt.
  • gebt dabei allen knoten eine Priority. Dann wird der Knoten mit der höchsten Priority bevorzugt master. Das mach es etwas vorhersagbarer
  • bei Bedarf kann man auch sog. "hidden" nodes anlegen (siehe Beispiel oben). Die sind ganz praktisch als "hot" backup (priority:0, hidden: true)

Viel Logik im Treiber

Der Treiber bei mongo ist relativ intelligent. Man startet ja nur die MongoD und teilt ihnen mit, dass sie sich in einem Replicaset befinden - that's it! Ein Treiber muss sich dessen "bewusst" sein und auf evtl. auftretende Fehler / Ausfälle reagiren. Klar, das ist nur kompliziert, wenn wir uns in einer replizierten / geclusterten Umgebung befinden. bei einem Single-Server-Setup ist ja alles simple. Aber wenn wir uns mit einem ReplicaSet "unterhalten" wollen, sieht das etwas anders aus.

So muss der Treiber als erstes mal rausfinden, welche Knoten gibt es überhaupt. Dafür übergibt man ihm einen sog. Seed. Das ist eine Liste von Serveradressen, da sollte tunlichst einer dabei sein, der auch antwortet. Diesen Fragt er dann nach dem ReplicaSet, welche Server gibt es, sind die alle da und wer ist der Master. Schreibzugriffe können nur auf dem Master-Server durchgeführt werden. Diese werden dann an alle anderen Knoten im Set repliziert (daher der Name).

Dummerweise kann sich der Master im Laufe der Zeit auch ändern - weil er z.B. runter gefahren wird oder sonst wie. Dann wird ein anderer Master gewählt und das muss der Treiber berücksichtigen.

Dieses Prinzip ist zunächst mal gewöhnungsbedürftig, aber es funktioniert - fast ;-) Das Problem ist, dass es auch im Treiber Fehler gab / gibt, die dann natürlich wie Replizierungsfehler oder so aussehen. Alles was ich hier sage bezieht sich auf die MonogDB-Version 2.4.x und die Programmiersprache Java. Und genau da gab es z.B. einen ziemlich nervigen Bug.

Wenn man eine Verbindung zur MongoDb über den Treiber herstellt, muss der sich natürlich mit mindestens einem Knoten im Cluster verbinden. Das klappt so weit auch, jedoch gab es in der Version 2.10.1 des Treibers bei uns den Fehler, dass er, obwohl der Knoten nicht verfügbar war, immer wieder Anfragen dort hin geschickt hat. Das hat zu unnötigen Exceptions und Timeouts geführt - und Downtimes. Zum Glück wurde der Fehler relativ schnell wieder behoben, und die aktuelle Version 2.11.1 hat diesen Fehler nicht.

Es gibt kein Compact-DB

Das ist wirklich ein Problem. MongoDb erzeugt sozusagen ein virtuelles Filesystem auf dem Dateisystem des Betriebssystems. Dabei werden, je nach Bedarf, immer wieder neue Dateien angelegt (bis zu 2GB), die dann die eigentlichen Daten beinhalten. So weit, so gut, jedoch wird der Speicherplatz nie wieder freigegeben. D.h. der Belegte speicherplatz wächst stetig.

Ähnliche Probleme gibt es auch mit MySQL oder PostgreSQL, aber auch eine Lösung: Das sog. Vaccuuming der Datenbank. Diese Funktionalität gibt es in Mongodb allerdings nicht. Es gibt dabei nur diese Möglichkeiten, das Problem in den Griff zu bekommen:

  • so viel Speicherplatz bereit stellen, dass das nicht passiert (klar, ist etwas blauäugig, aber kann eine ganze Weile gut gehen). Vorteil: keine Downtime
  • das Kommando repairDatabase regelmäßig ausführen, bzw. den mongod mit der Option --repair starten. Dieses hat jedoch ein paar Tradeoffs: Während dieser Aktion ist der Knoten offline, nimmt keine Anfragen entgegen und es wird im endeffekt das gesamte Datenverzeichnis an Ort und Stelle kopiert => man benötigt doppelt so viel platz, damit das überhaupt möglich ist.
  • In regelmäßigen Abständen den mongod stoppen, das Datenverzeichnis komplett löschen und den Knoten neu synchronisieren lassen. Das ist zwar auch mit Downtime verbunden, aber braucht wesentlich weniger spare space wie die repairDatabase-Option - geht nur im Replicaset, klar, oder?
  • Die Datenbank auf einem Knoten mit einem der oberen beiden Varianten "komprimieren" und das Datenverzeichnis dann auf die anderen Knoten kopieren (z.B. mit rsync). Achtung: die MongoD auf den Zielknoten _müssen_ gestoppt sein! Der mongoD darf nicht laufen! Denn sonst wird das ganze Datenverzeichnis gelöscht. Auch nicht wünschenswert... Das löschen kann man erreichen, indem man einfach mit kill den mongod prozess beendet (Bitte nicht mit Kill -9!)

Wir haben uns für die vorletzte Variante entschieden. Jede Woche wird ein anderer Knoten komprimiert und seit der Treiber währenddessen keine Fehler mehr produziert, klappt das auch ohne Downtime.

Falls jemand noch eine Idee hat, was man tun kann, dann bitte per Kommentar unten hinzufügen.

Mongo ist extrem RAM- und IO-lastig

Eigentlich logisch, oder? Mongo nutzt Memory Mapped files, um schnell auf die Daten zugreifen zu können. Dazu müssen die aber auch ins RAM passen (bzw. zumindest die Files, die die Indices beinhalten).

Wenn man jetzt noch regelmäßige Updates auf den Daten fährt, ist es zwingend nötig, dass das Storage schnell genug ist. Bei uns gab es diesbezüglich einen Engpass, mit dem Effekt, dass mongo beim schreiben (und manchmal auch beim Lesen) Timeouts geliefert hat (die Zeitspanne kann konfiguriert werden). Das war super schwer zu finden, lag bei uns aber an einem überlastetem Storage.

Es wird auch empfohlen den Read-Ahead des Devices, auf dem die Mongo-Daten liegen, möglichst runter zu drehen. Empfohlen wird ein wert von 32 Blöcken / 16 KB. Unter linux kann man das recht einfach machen mit

blockdev --setra 32 /dev/sdb1

Mongo ist extrem abhängig von Indizes

Das ist das wichtigste überhaupt: Für alle Suchenzugriffe auf etwas größere oder große Collections (= Table in SQL-Nomenklatur) ist es nahezu zwingend nötig, einen brauchbaren Index definiert zu haben. Denn bei einem "FullTableScan" wartet man bei Mongo laaaaaaange!

Plant eure Datenstruktur etc entsprechend, legt sinnvolle Indizes an. Bei der Datenstruktur sind auch Redundanzen kein Problem (ist ja etwas, was man bei SQL tunlichst vermeidet). Aber lieber etwas zu doppelt gespeichert aber die Zugriffszeiten gezehntelt, als am Platz gespart.

Mongo benötigt Struktur

Ja ja, NoSQL-Datenbanken sind ja so schlecht, weil sie keine Struktur haben. Falsch: sie erzwingen keine. Benötigt wird sie aber dennoch! Ist sogar nahezu zwingend erforderlich. Achtet darauf, dass Indizes auf euren Collections sind - nur so könnt ihr performant mit der Mongo arbeiten.

Der große Vorteil der Schemalosigkeit kann auch schnell zum Nachteil werden, wenn man nicht aufpasst. Da ist Programmierdisziplin gefragt.

Wenn möglich sollte man einen Object-Mapper verwenden, damit man nicht direkt auf der Mongo umprogrammiert. Wir nutzen Morphium als Mapper, da das auch Caching, Deklarative Validation und einige andere Features mitbringt.

Es gibt keine "Joins"

Das wird gerne mal vergessen, mongoDb kann nicht joinen. D.h. man muss seine Datenstruktur auch entsprechend designen. Joins sind in Mongo deswegen nur möglich, wenn man sie ausprogrammiert, oder in MapReduce oder dem aktuellen Aggregation-Framework abbildet. Das ist alles viel mächtiger als reine Joins - allerdings auch viel langsamer!

Falls man in seinem Programm an eine Stelle kommt, wo man zwingend joinen muss, sollte man sich evtl. fragen, ob die Datenbank korrekt designed ist.

Mongo ist schnell

Das ist wirklich ein wichtiges Kriterium: Mehrere 10.000 Schreibzugriffe pro Minute sind kein Problem, zeitgleiches Lesen auch nicht. Jedoch muss man auf die obigen Hinweise achten, sonst kann das nicht funktionieren.

Achtung: auch hier gilt wieder, wenn das storage lahm ist, kann Mongo nicht schnell sein! Das mussten wir am eigenen Leib erfahren, das unser Storage leider doch so seine Tücken hat:

BNCeuGdCAAAM-w1.jpg-large

 

Das ist übrigens ein Beispiel dafür, wie wir Graphite einsetzen und was wir alles messen. In diesem Fall sind des die LAufzeiten der Lese und Schreibzugriffe. Das konnten wir recht leicht realisieren, da morphium eine Schnittstelle für sog. Profiling Listener bietet. Dort mussten wir die Daten dann nur noch nach Graphite schicken.

Überwachung der Mongo-Knoten

Da gibt es auch verschiedene Möglichkeiten. Die Mongo Installation liefert schon einige Tools, mit denen man was tun kann:

  • mongotop zeigt an, wie lange Schreib- und Lesezugriffe auf bestimmten Daten dauern.
  • mongostat zeigt mehr über technischen Hintergrund, wie viel ram etc.
  • mongosniff zeigt den von mongo verursachten traffik an, ähnlich tcpdump

Wer ein wenig scripten kann, kann natürlich auch selbst was bauen, oder man nutzt den kostenfreien Service von 10Gen namens MMS.

Leider frisst der MMS-Agent bei uns binnen kürzester Zeit 80% RAM, so dass nix anderes mehr läuft. Eine Lösung konnten wir bisher noch nicht finden.

Die Überwachung der Mongo geht aber auch "zu Fuß", d.h. mit ein wenig Bash- oder sonstwie Skripterei. Der Mongo client kann alle möglichen Informationen über das Replicatset auslesen. z.B. der ReplicationLag, d.h. wie viel ein Knoten hinter dem Master-Knoten her hinkt. das ist eine wichtige Kennzahl, die viel darüber aussagt, ob z.B. das storage passt.

So kann man den Status des Replicaset inkl des ReplicationLag (also wie weit welcher Knoten hinter dem Master her hinkt) mit dem Kommando:

mongo host:port/datenbank --eval 'rs.status()'

die Ausgabe ist sehr ausführlich und liefert den Status eines jeden Mongo-Knoten:

{
 "set" : "tst",
 "date" : ISODate("2013-06-18T18:56:58Z"),
 "myState" : 2,
 "syncingTo" : "127.0.0.1:27018",
 "members" : [
 {
 "_id" : 0,
 "name" : "127.0.0.1:27017",
 "health" : 1,
 "state" : 2,
 "stateStr" : "SECONDARY",
 "uptime" : 96,
 "optime" : {
 "t" : 1370329384,
 "i" : 1
 },
 "optimeDate" : ISODate("2013-06-04T07:03:04Z"),
 "errmsg" : "syncing to: 127.0.0.1:27018",
 "self" : true
 },
 {
 "_id" : 1,
 "name" : "127.0.0.1:27018",
 "health" : 1,
 "state" : 1,
 "stateStr" : "PRIMARY",
 "uptime" : 93,
 "optime" : {
 "t" : 1370329384,
 "i" : 1
 },
 "optimeDate" : ISODate("2013-06-04T07:03:04Z"),
 "lastHeartbeat" : ISODate("2013-06-18T18:56:57Z"),
 "lastHeartbeatRecv" : ISODate("1970-01-01T00:00:00Z"),
 "pingMs" : 0
 },
 {
 "_id" : 2,
 "name" : "127.0.0.1:27019",
 "health" : 1,
 "state" : 2,
 "stateStr" : "SECONDARY",
 "uptime" : 93,
 "optime" : {
 "t" : 1370329384,
 "i" : 1
 },
 "optimeDate" : ISODate("2013-06-04T07:03:04Z"),
 "lastHeartbeat" : ISODate("2013-06-18T18:56:57Z"),
 "lastHeartbeatRecv" : ISODate("1970-01-01T00:00:00Z"),
 "pingMs" : 0,
 "lastHeartbeatMessage" : "syncing to: 127.0.0.1:27018",
 "syncingTo" : "127.0.0.1:27018"
 }
 ],
 "ok" : 1
}

Wir senden diese Daten dann noch in Graphite um diese bei bedarf auch historisch auswerten zu können.

Des weiteren bekommt man so auch gleich mit, ob der Knoten noch up ist, oder nicht. Da wir Graphite auch für viele andere Werte benutzen, war es für uns naheliegend, das so zu implementieren.

Interaktiv kann man sich das ganze natürlich einerseits über die Shell oder über das Web-Interface ansehen. Wenn man auf Port 28017 des Servers geht (mit dem Webbrowser) so erhält man da noch weit mehr Informationen, wie z.B. wie viele und welche Verbindungen sind offen, welche Requests laufen gerade etc.

Mongo kann keine Transaktionen

ACID sucht man bei Mongo vergeblich, schon gar nicht verteilt. Standardmäßig ist jede Schreiboperation ein "Fire and forget"-Vorgang. Der Schrieibzugriff wird weiter gegeben, und das wars.

Man kann da zwar ein wenig Sicherheit erreichen, in dem man dem Treiber mitteilt, er soll gefälligst darauf warten, dass die Daten geschrieben wurden oder von anderen Knoten bestätigt... aber das ist weit von Transaktionen entfernt.

Für das Messaging von morpium habe ich eine Art "Transaktion-Light" auf Mongo eingerichtet. Die Vorgehensweise basiert darauf, dass Update-Prozesse atomar ablaufen und die ACID Bedingungen erfüllen. In Pseudo-Code sieht es so aus:

  1. Blocke ein Entry für die Bearbeitung, indem eine eigene eindeutige ID an den Eintrag ran geschrieben wird.
  2. Lese alle mit meiner ID geblockten Einträge und bearbeite sie
  3. Entferne meine ID von allen Objekten

Das ist nicht 100% fehlerfrei, hat in meinen Tests aber ganz gut funktioniert. Das würde natürlich noch zu Dirty-Reads führen, man müsste halt alle Lesezugriffe um den Zusatz "blockedID==null" erweitern... dann würde es relativ einfach funktionieren.

Wenn man noch ein Blocking auf Collection-Ebene einführen wollte, würde das auch gehen.

ABER: man muss quasi das Transaktion-Handling komplett selbst implementieren. Und dann noch an "falscher" Stelle im Application Stack. Denn egal wie man es dreht, die Logik wäre Teil der Application und nicht teil der Datenbank. Das kann also keinesfalls so sicher sein, wie eine "echte" Transaktion.

Das Kann ein K.O.-Kriterium sein, das gegen die Verwendung von Mongo spricht!

Fazit:

wir hatten so unsere Schwierigkeiten mit der MongoDB, wobei die (fast) völlig unschuldig war. Es lag am Storage oder am Java-Treiber für Mongo. Jetzt läuft wieder alles stabil und flott und zusammen mit Morphium haben wir ein richtig gutes Setup erreicht.

Ich kann mongo nicht uneingeschränkt empfehlen, man sollte sich schon klar werden, ob es einem Vorteile bringt, ob die Nachteile evtl. nicht überwiegen.

Ich hab jetzt mehrere Projekte mit MongoDB realisiert und in diesen Fällen ist es auf jeden Fall richtig gewesen.


Kategorie: Computer --> Programmierung

Objective-C vs Java - Memory Management

Mi, 29. 05. 2013 - Tags: java memorymanagement objective-c

Bei meinen Entwicklungen in Java und Objective-c musste ich mich zwangsläufig auch mit dem Memory Management auseinandersetzen. Das ist in Java meistens ja etwas, das insbesondere seit JDK 1.4 mehr oder minder im Hintergrund stattfindet. Das war eine der großen Neuerungen von Java und viele andere Sprachen haben es übernommen. Aber gerade das aufkommen der neuen mobilen Endgeräte mit doch mehr oder minder beschränkten Resourcen machen das MemoryManagement der eigenen Applikationen wieder etwas spannender.

Warum überhaupt Speichermanagement?

Früher war doch alles besser - im guten alten Assembler hat man einfach so im Speicher rumgeschrieben, ohne sich einen Dreck um irgendein Betriebssystem oder so zu kümmern. Das ist heute natürlich nicht einfach so möglich, denn das Betriebssystem benutzt ja auch RAM und man ist ja nicht das einzige Programm, das gerade läuft. So gesehen, war die gute alte 8-Bit Ära schon besser, weil einfacher! Heute muss man erst mal das System fragen, wenn man Speicher benutzen will, denn sonst wird das Programm schnell einfach beendet (Unter Unix ist das gerne mal ein „Segmentation Fault“) - schon aus Sicherheitsgründen - man könnte ja sonst wichtige Systemfunktionen oder ~daten überschreiben. Speicher allokiert man z.B. in C/C++ mit der Funktion malloc. Ihr gibt man einfach eine Größenangabe mit und als Ergebnis erhält man einen Zeiger auf den für uns zur Verfügung gestellten Speicherbereich. Das Gegenstück zu mallocist free- damit wird zuvor allokierter Speicher wieder frei gegeben. Alles eigentlich doch recht simpel. Naja… da könnte man doch fragen, was denn daran so kompliziert ist, den Speicher, den man sich vorher „geholt“ hat, dann auch wieder frei zu geben, wenn man ihn nicht mehr braucht. Im Grunde ist das richtig, vor allem für sehr einfache Programme kann man den Speicher so „verwalten“. Aber heutige Anwendungen haben schon andere Anforderungen an das Speichermanagement. Das fängt schon damit an, dass ich zum Erstellungszeitpunkt gar nicht genau weiß, wie viel Speicher ich eigentlich benötige. Person p=new Person(); p.setName(„Ein Name“); p.setAge(new Integer(44)); sieht simpel aus, oder? aber genaugenommen passiert folgendes: es wird Speicher für ein Objekt „Person“ allokiert. Die Person ist noch „leer“. Jetzt wird ein Name hinzugefügt. D.h. zum Zeitpunkt der Erstellung der Person wusste ich nicht, wie groß das Objekt eigentlich werden sollte. Ich kann also gar nicht wissen, wie viel Speicher ich bereit stellen muss.

Retain Cycle

Gleiches gilt für die Freigabe. Wann soll ich den Speicher wieder frei geben? Wann wird die Person pnicht mehr benötigt? Das wird besonders dann komplex, wenn man auch komplexere Datenstrukturen benötigt. Ein einfaches Beispiel dafür ist eine Baumstruktur:

 Class Parent extends Node{
      List children;
    }
    Class Child extends Node {
       Parent parentNode;
    }

solch eine Struktur ist normalerweise im Vorhinein nicht klar in der Größe festzulegen. Außerdem ergeben sich sog. Retain Zyklen. Hier hat jeder Knoten einen Zeiger auf seine Vaterknoten und dieser wiederum einen Zeiger auf alle Kindknoten. Das bedeutet, jeder dieser Knoten ist von irgendwoher erreichbar, es gibt ja eine Referenz darauf. Das Hauptprogramm:

root = new Parent();
//add children
// do something with root and the Data
Root = new Parent()

Das Hauptprogramm erzeugt hier einen Parent-Knoten mit alle seinen Kindknoten. Der der so belegte Speicher ist noch vom Hauptprogramm aus erreichbar, da wir ja die Referenz root haben. Wird diese allerdings ersetzt (letzte Zeile), ist der ganze Baum nicht mehr erreichbar und der Speicher könnte theoretisch freigegeben werden. Allerdings ergibt sich ein Retain Zyklus, da ja jeder knoten von einem anderen referenziert wird => Dilemma.

Wie funktioniert das in java - der Garbage Collector

Den Garbage Collector (oder kurz GC) kann man sich vorstellen, wie die Mutter eines Teenagers, die immer hinter ihm herläuft und auf- bzw. wegräumt. Genauso ist der Garbage Collector. Der GC erkennt die Objekte, die vom Programmthread (bzw von allen Programmthreads) aus erreichbar sind. Alle anderen werden gelöscht. Dafür erstellt der GC einen sog. Erreichbarkeitsgraphen aller erstellten Objekte. Und der Speicher aller Objekte, die vom einem laufenden Thread aus nicht mehr erreichbar sind, wird freigegeben. Klingt im ersten Moment recht simpel, wird aber insbesondere im Hinblick auf Retain Cycle etwas undurchsichtig. Kurz gesagt, der GC kann auch komplexere Datenstrukturen einfach aus dem Speicher räumen, da er ja die Erreichbarkeit von den Programmthreads aus berechnet und nur die Objekte abräumt, die nicht mehr erreichbar sind. => auch Retain Cycle werden abgeräumt. Manchmal wird geschrieben, dass der GC Retain Cycle erkennt, da er ja alle darin befindlichen Objekte im Speicher hält, wenn es auch nur eine Referenz auf das Objekt gibt.

Wie obiges Beispiel: Baumstruktur. Vaterknoten hält Referenzen auf Kindknoten und umgekehrt. Wenn der Vater abgeräumt wird, weil er nicht mehr erreichbar ist, werden auch alle Knoten, die daran hängen mit abgeräumt (es sei denn, das Hauptprogramm hält noch eine referenz auf einen der Kind-Knoten. Dann würde zumindest dieser Baumteil nicht mit abgeräumt).

Die erste Instanz von Parent() mit all seinen abhängigen Kindknoten sind am Ende des Beispiels zwar noch im RAM, aber vom den Programmthreads aus nicht mehr erreichbar. D.h. Der GC wird sie beim nächsten Lauf abräumen.

Manchmal ist es auch wichtig, diese Cyclen vom Programmseite aus zu verhindern bzw. zu vermeiden. Dafür gibt es in Java z.B. noch spezielle Referenzobjekte WeakReference und PhantomReference. Diese verhalten sich etwas anders beim berechnen des Erreichbarkeitsgraphen und beeinflussen so den GC. Eine Normale Variable ist in dem Zusammenhang immer als strong Referenz zu sehen.

Wie klappt das in Objective-C?

In Objective-C wird seit geraumer Zeit ein etwas anderer Ansatz gewählt, das Reference Counting. Dabei hält jedes Objekt einen Zähler, wie viele Zeiger darauf verweisen. An sich eine gute Idee, jedoch muss der Programmierer leider selbst dafür sorgen, dass diese Zähler erhöht oder verringert werden. Dafür stellt Objective-C die beiden Methoden retain und release bereit, die von NSObject aus vererbt werden. Mit retain wird der Reference Counter (auch Retain Counter) erhöht, mit release um eins reduziert. Ist der Reference Counter gleich 0, kann das Objekt gelöscht und der zugehörige Speicher freigegeben werden. Das war fürchterlich mühsam und auch fehleranfällig. Man musste höllisch aufpassen, wann man retain und wann release aufruft. Wurde letzteres zu früh aufgerufen, hatte man irgendwo einen nil-Pointer. Das verursacht zwar nicht unbedingt eine Fehlermeldung in Objective-C (zugriff auf nil pointer tut meistens einfach nix), aber tut eben auch nicht das, was man erwartet. Wurde ein release jedoch vergessen, hatte man ein Memoryleak produziert - auch nicht gerade wünschenswert.

ARC

In den neuen Versionen von Obective-C (OSX und IOS6) wurde das „Automatic Reference Counting“ eingeführt. Das einem diese ganze lästige Arbeit abnehmen sollte. Das funktioniert eigentlich wirklich super - wenn man ein wenig Hintergrundinfos hat. Im Endeffekt wird das Hauptprogramm nur in einem @autoreleasepool { } gestartet, was der Laufzeitumgebung sagt: „Hey, alles in diesen Geschweiften klammern bitte zählen“ Das funktioniert weitgehend zur Compiletime, d.h. der Compiler fügt Code ein, der die Referenzcounter erhöht oder verringert! Zur Laufzeit findet nur noch eine Prüfung auf reference_counter==0 statt. Der Ansatz ist also grundlegend anders als der des Garbage collectors. Auch birgt er so seine Tücken.

So kann mit dieser Methode ein Retain Cycle nicht erkannt werden bzw. die so belegten Speicherbereiche werden nicht freigegeben => Speicherloch! Um das zumindest von Programmiererseite verhindern zu können, kann man auch in Objective-C schwache weak referenzen verwenden. Nehmen wir die Baumstruktur noch mal als Beispiel, hier in Objective-C. Parent.h:

 @interface Parent: Node
 @property (strong, nonatomic) NSArray *children;
 @end

Child.h:

@interface Child: Node
@property (weak, nonatomic) Parent *parent;
@end

wichtig ist, dass der Child-Knoten nur eine weak-Referenz auf den Vaterknoten hat. Damit kann der ganze Baum abgeräumt werden, wenn der Vaterknoten nicht mehr erreichbar ist. im Programm:

Parent *p=[[Parent alloc]init];
//add children
//do something
p=[[Parent alloc]init];

Eine weak-Referenz erhöht den Reatain-Counter nämlich nicht. Wenn also das Hauptprogramm keinen Zeiger mehr auf den ursprünglich erzeugten Objektbaum hat, kann alles abgeräumt werden. Bei meinen Tests mit IOS7 ist mir aufgefallen, dass es sehr wohl sinnvoll sein kann, Objektpointer auf nil zu setzen. Wenn man das tut, wird der Speicher sofort wieder freigegeben. Und in einigen Fällen ist es das, was man braucht. (zur Erinnerung, das meiste beim ARC wird als Code automagisch eingefügt! Wenn ich irgendwas auf nil setze, weiß der Compiler auf jeden Fall, dass ich das Objekt nicht mehr brauche).

Aber das ist leider nicht alles. Das ARC wird von den Grenzen des Code-Blocks von @autoreleasepool bestimmt. Das bedeutet, erst wenn man den Block verlässt, wird sicher alles abgeräumt, was noch mit einem Retain-Counter von 0 im RAM rumidelt. So kann es nötig sein, dass man von Zeit zu Zeit mal einen neuen Autoreleasepool anlegt, damit die darin erzeugten Zwischenergebnisse vom Heap entfernt werden. Es ist schwer zu sagen, wann genau man das machen muss, aber Rekursionen und Schleifen sind heiße Kandidaten ;-)

Leider kommt man in Objective-C nicht immer nur mit den Objekten aus, die einem die Laufzeitumgebung zur Verfügung stellt. Da Objective-C eine Obermenge von C ist, kann man auch auf die gesamte C Funktionalität zurückgreifen und das ist leider manchmal nötig. Einige Systemaufrufe liefern als Ergebnis leider C-Structs und keine Objekte, die mit ARC wieder abgeräumt werden. Manchmal muss man solche Datenstrukturen auch anlegen, was dann natürlich am ARC „vorbei“ passiert. So kann man sich sehr einfach ein Speicherloch züchten, wenn man nicht aufpasst. Im Zweifel am besten immer auf die Objekte der Laufzeitumgebung setzen.

Bewertung beider Mechanismen

GC

  • Nachteil: die Laufzeianalyse ist evtl aufwändig, kann zu kurzzeitigem "einfrieren" führen insbesondere bei speicherintensiven Anwendungen, läuft irgendwann - der Zeitpunkt ist nicht klar
  • Vorteil: simpel für den Programmierer, Retain Cycle werden erkannt

ARC

  • Vorteil: keine aufwändige Laufzeitanalyse, alles zur Compiletime (Set Nil deswegen manchmal sinnvoll)
  • Nachteil: es wird automatisch Code erzeugt, das kann zu unerwartetem Verhalten führen, keine Erkennung von cyclen Problem: Memory allocation an dem Arc vorbei!


Kategorie: Computer

MongoDB Problem

Mo, 22. 04. 2013 - Tags:

Das ist eigentlich der größte Nachteil an Mongodb... immer wieder klemmt irgendwas in der produktiven Umgebung. Und man wird da ein wenig allein gelassen. 10Gen will dafür echt viel Geld, die MongoDB-User-Group in Google hilft einem auch nicht viel weiter (wie bei dem wirklich nervigen Timeout-Bug -> immer noch keine Antwort).

Jetzt gibt’s wieder was neues mit der Aktuellen Version 2.4.2 -> beim Syncen eines Knotens hängt der gesamte Cluster, alles wird langsamer. So was dämliches... echt. Deswegen hatten wir gestern (natürlich am Wochenende) 2 Downtimes...

Ich hab den Fehler mal gepostet, aber ich hoffe echt, dass wir die Fehler bald in den Griff... sonst bleibt uns ja quasi nix anderes übrig, als Mongo zu ersetzen über kurz oder Lang!

Hier kann man den Bug auch noch mal nachlesen: https://groups.google.com/forum/?fromgroups=#!topic/mongodb-user/LXXq67EPoKI

Der Timeoutbug wird hier http://code.google.com/p/morphium und hier https://groups.google.com/forum/?fromgroups=#!topic/mongodb-user/EA-isu6cc8c beschrieben

 


Kategorie: Allgemeines

Wer ist Stephan Bösebeck?

Fr, 19. 04. 2013 - Tags: about

Ursprünglich veröffentlicht auf: https://boesebeck.name

Wessen Blog ist das hier? Ja, wer bin ich... diese Frage ist erstaunlich schwierig zu beantworten, dummerweise. Mein IT-Werdegang ist auf hier im Blog unter Meine IT Geschichte nachzulesen. Da gibt’s es nicht viel mehr zu sagen. Privates wird es hier nur wenig geben, deswegen nur noch mein beruflicher Werdegang in aller Kürze:

  • Nach dem Abi (und einer recht dämlichen Episode bei der Bundeswehr - aber das ist eine andere Geschichte) hab ich Informatik an der Uni Passau Studiert
  • Abschluss mit Diplom
  • Da mir nur wenig bis kein BaFöG zustand und meine Eltern auch nicht sonderlich viel beisteuern konnten, habe ich schon während des Studiums angefangen zu arbeiten
  • Abgesehen von einigen Studentenjobs (z.B. beim Computer Discounter ESCOM - leider pleite mittlerweile, hab da aber viel gelernt) war ich ab dem Jahr 1995 als IT-Consultant unterwegs
  • Ich habe in dieser Zeit auch einige Zertifizierungen gemacht: Sun Certified Trainer, Certified Java Programmer, LPI Zertifizierung, Certified C++ Programmer, Certified Macro4 Trainer, usw. - Ich könnte vermutlich ganze Zimmer mit dem Zeuch tapezieren
  • Kunden von mir waren unter anderem: IBM, Sun, Dresdner Bank, Deutsche Bank, HP,  Unilog Integrata, Ixos Training, Goethe Institut...
  • Ich hab mich auf die Technologien Linux/Unix und Java spezialisiert. Deswegen auch die Zertifizierungen in den Bereichen
  • Ich habe vor allem für SUN und Integrata Java Schulungen gemacht, angefangen von Beginner bis hin zu Advanced Java Programmer Zertifizierungs Schulungen
  • Ähnlich sah es mit Linux Schulungen aus, u.A. für IBM. Angefangen von Beginner-Schulungen bis hin zu Advanced Clustering und Firwalling Themen -Natürlich gab es auch Schulungen in anderen Bereichen rund um Unix: Perl, TCL/Tk und noch ein paar andere Exoten (Python war damals noch echt exotisch emoji people:smirk)
  • Das Wissen für die Schlulungen hab ich mir in vielen, leider meist recht kleinen Projekten angeeignet. Da gab es eine Menge klein- und mittelständische Unternehmen, für die ich Software Entwickelt habe. Oder Zuarbeit geleistet habe bei größeren Projekten.
  • Da ich die Projekte sehr häufig dann doch nicht allein realisieren kann, wurde ich sehr schnell auch in die Rolle des Projektleiters / ~managers "gedrückt". Als Ansprechpartner für die Kunden, als Auftraggeber für Subunternehmer etc.
  • Da ich als Freiberufler allein kaum eine Chance hatte, mal ein großes Projekt zu leiten oder zu realisieren hab ich mich entschlossen, fest angestellt zu arbeiten. In dem Fall habe ich bei Softlab als Projektleiter angefangen
  • Dort durfte ich einige größere Projekte Realisieren (naja, zumindest größer als das vorher). Unter Anderem für MAN und BMW
  • Ich wollte mehr Management-Erfahrung sammeln und wurde dann von Acronis "abgeworben". Ich hab dort als "Manager Training & Consulting EMEA" angefangen. und musste dort die Trainings-Abteilung aufbauen. Zunächst nur für Deutschland, später für EMEA, dann weltweit.
  • Die Trainings-Verwaltung und das Trainings-Management ist dann mehr oder minder nach USA umgezogen, weshalb ich dann "Head of Engineering" bei Holidayinsider.com wurde. Leider hat HRS uns aufgekauft und mittlerweile gibt es Holidayinsider auch nicht mehr
  • Ich war dann etwas mehr als ein Jahr Head of Technology bei Simplesystem GmbH & Co Kg - das war aber leider nicht so ganz das richtige
  • der Nächste Job ist schon fix, werde ich aber erst hier posten, wenn ich da angefangen habe emoji people:smile


Kategorie: Computer

PostgreSQL => MongoDb => Morphia => Morphium

Do, 18. 04. 2013 - Tags: java mongodb morphium

Das war ein langer Weg... Bei holidayinsider.com nutzen wir mittlerweile MongoDB (neben Lucene-SolR als primäre SearchEngine) als primäres BackEnd. Das brachte ein paar Probleme aber auch tolle Lösungen mit sich.

Zunächst mal, warum überhaupt eine NoSQL-Db und warum dann gerade Mongo? Die Frage ist natürlich berechtigt und wurde bei uns auch heiß diskutiert. Für das, was wir vor hatten, war PostgreSQL (so toll die Datenbank auch sonst ist) leider nicht ganz das richtige. Die Vorteile (Transaktionen, Berechtigungen und Relationen) wären für unseren Ansatz nur hinderlich gewesen.

Was haben wir gemacht? Wir loggen nahezu alles, was auf der Plattform passiert und werten diese Daten dann später in einem Offline-Prozess aus (wobei auch eine Menge Daten live in Graphite mit verfolgt werden können). Das war so mit PostgreSQL nicht möglich - wir hatten zwischenzeitlich Collections mit mehr als 100 Millionen Datensätzen deren Verarbeitung mit PostgreSQL mehr als 24h gedauert hätte. Da diese Daten keinen Relationen zuzuordnen sind, brachte eine Relationale Datenbank mit Transaktionen etc keine besonderen Vorteile. Deswegen der Gedanke (auch, weil das Mapping den Java-Objekten recht nahe kommt) auf einen Dokumentenbasierten Storage und in dem Fall Mongo zu setzen. Und: wir sind ein hippes Startup Unternehmen, da muss man auf neue Technologien setzen!

Warum Mongo? Wir wollten auf ein möglichst flottes und einfaches Modell setzen, Mongodb als primäre Datenbasis war das erklärte Ziel, PostgreSQL "nur" noch im Hintergrund für einige nicht lastabhängige Daten. Mongo schien uns als erste Wahl in dem Zusammenhang... da könnte man in der Retrospektive sicherlich noch mal drüber diskutieren ;-)

holidayinsider.com ist eine 100% pure Java Anwendung ohne viel Overhead. Deswegen war es auch wichtig, eine gute Java Anbindung der NoSQL-Datenbank zu haben. Für Mongo gab es 1. einen guten Java-Treiber, und 2. ein Mapping Framework namens Morphia.

Wir haben morphia einige Zeit lang produktiv in der Anwendung gehabt, aber insbesondere mit der Erweiterbarkeit hatten wir Probleme. Als dann die ersten Bugs in morphia zu Datenverlusten und DownTimes geführt hatten, mussten wir uns was einfallen lassen. Insbesondere, weil Morphia seit bestimmt einem Jahr auf der Version 0.99-1 festhängt und nicht mehr weiter entwickelt wird. Wir hätten bei dem Projekt ja sogar mitentwickelt, haben das auch angeboten, aber der einzige Entwickler von Morphia wollte nicht, dass irgendjemand mit macht.

Wir wollten das Projekt forken, haben es ausgecheckt und ich hab mir den Code mal genauer angesehen. Sagen wir es so - es war einfacher, es neu zu machen.

Das war die Geburtsstunde von Morphium (http://code.google.com/p/morphium) - der Name war an Morphia angelegt, ich wollte so auch die Vorarbeit von Scott Hernandes würdigen. Das Projekt wird rege weiter entwickelt und ist mittlerweile in der Version 2.0.6 verfügbar. Entweder von der Projektseite, oder von SonatypeOSS / Maven.

Morphium ist ein ObjectMapper für Java mit einer Menge Features (die meiner Meinung nach in Morphia gefehlt haben). Hierbei bezeichnen Entities Objekte (POJOs), die in Mongo gespeichert werden sollen.:

  • sehr robustes, flexibles und performantes Mapping von Entities/POJOs zu mongo Objekten: So ist es in Morphium möglich, seinen eigenen Mapper zu definieren. Das ist z.B. sinnvoll, wenn man das Mapping mocken muss für z.B. JUnit-Tests oder dergleichen.
  • Flexible API: Es ist möglich, fast alle Kernkomponenten selbst zu implementieren bzw. die Default-Implementierungen abzuleiten und um eigene Funktionalitäten erweitern. so können ObjectMapper, Cache, Writer und Query durch eigene Implementierungen ersetzt werden (zur Laufzeit).
  • Unterstützung von Vererbung und Objekthierarchien: Morphium untersucht für das Mapping den gesamten Ableitungsbaum, wodurch es möglich ist, Entities von anderen abzuleiten und entsprechend zu erweitern.
  • Deklarative definition von Indices: Mongo ist nur dann performant, wenn indices verwendet werden. Diese sollten gut zu den Suchanfragen passen. Ohne Index hit ist eine Query um leicht das 10-100 fache langsamer (query auf eine Collection mit ca. 500.000 Entries ohne Index hit dauerte ca. 45 Sek - mit < 1Sek). In Morphium können die Indices mit Hilfe von Annotations definiert werden.
  • Deklaratives mapping: Feld- und Collectionnamen können deklarativ festgelegt werden. Es ist auch möglich, Aliase anzugeben, wordurch ein Feld evtl. unter mehreren Namen ansprechbar ist. Das ist insbesondere bei Datenmigration hilfreich. Es ist auch möglich, daten von verschiedenen Typen in eine Collection zu speichern - insbesondere bei Vererbung sinnvoll, weshalb das Flag auch @polymorph heißt.
  • transparente Referenzen (werden von Morphium aufgelöst), incl. lazy loading: In Morphium kann man sehr leicht objekt-Referenzen definieren. Das funktioniert über die Java-Annotation @Reference. Diese wird direkt auf dem entsprechenden Feld angewendet, wodurch nicht das ganze
  • deklaratives Caching: jedes entity bestimmt, ob es per default gecachted werden soll oder nicht. Das bedeutet, dass Ergebnisse von Suchanfragen an diese Entity im Ram gehalten werden. Dabei wird auch bestimmt, wie groß dieser Cache sein soll, ob er bei Schreibzugriffen geleert wird, wie lange Cache-Einträge gültig sind und was passieren soll, wenn der Cache "voll" ist.
  • asynchrones und gepuffertes Schreiben: gerade die Schreibzugriffe im Cluster (wenn man darauf wartet, dass die Knoten die einzelnen Schreiboperationen bestätigen) können länger dauern. Das kann zu Problemen mit der Performance führen. Eine Asynchrone API war da sehr sinnvoll. Gepuffertes Schreiben legt zunächst einen Puffer im RAM an und schreibt die Daten dann erst nach einer gewissen Zeit, oder wenn eine Mindestanzahl erreicht ist. Das ist insbesondere deswegen sinnvoll, weil Bulk-Inserts deutlich effizienter funktionieren als einzelne.
  • Validation: Validieren der Daten vor jedem Schreibzugriff mit javax.validation
  • Partial updates: gerade bei großen Dokumenten sinnvoll - sendet Änderungen an die Datenbank, nicht das gesamte Objekt
  • Threadsafety und Cluster awareness bzw. Clusterfähigkeit: Wir nutzen Mongo im cluster von mehreren Knoten. Es ist wichtig, das Morphium Unterstützung dafür hat. Einerseits um die Sicherheit zu gewährleisten (warte darauf, dass alle verfügbaren Knoten den Schreibzugriff bestätigen) andererseits um z.B. richtig zu reagieren (wenn ein knoten ausfällt etc.). Das ganze wird innerhalb Morphiums auch überwacht
  • Montoring und profiling: durch ein cleveres Listener-Konzept ist es möglich, jeden Schreib- oder Lesezugriff zu messen und die Daten für spätere Auswertung abzulegen (z.B. in Graphite).
  • Lifecycle Methods: Es ist möglich, Callbacks innerhalb eines Entity zu definieren für "preStore" oder "postLoad". Des Weiteren ist es noch möglich, globale Listener für diese Events zu definieren, d.h. bei jedem Schreibzugriff bzw. Lesezugriff unbabhängig vom Typ informiert werden.
  • automatische Werte: wie z.B. lastChanged oder lastAccessed - Timestamps
  • einfaches Messaging: messaging über MongoDB. Die Messages werden persistiert und in einer Transaktions-ähnlichen Art und Weise verarbeitet.
  • Unterstützung für Geospacial-Suche: selbsterklärend, oder?
  • Unterstützung von Map-Reduce bzw. dem Aggregation Framework: Das ist die Antwort von Mongo auf Joins und komplexe Anfragen in der "Relationalnen" Welt. Kurz gesagt: damit lassen sich auch schwierigere Auswertungen einigermaßen performant lösen. Das Konzept dahinter ist aber dennoch etwas schwer zu verstehen.
all diese Features sind zur Zeit in der aktuellen Version von Morphium verfügbar und sind auch schon produktiv in einigen Projekten im Einsatz.

Weitere Doku ist auf der Projektseite oder unter www.caluga.de/morphium zu finden.

Aber zurück zum Thema mongoDB. So einfach, wie ich es hier geschildert habe, war es auch nicht. Wir hatten auch ein paar (u.a. schmerzhafte) learnings.

Wir hatten 10Gen zu uns eingeladen, uns die Vorteile von Mongo mal zu erklären und uns das in ihren Augen korrekte Setup zu empfehlen. Wir nutzten zu diesem Zeitpunkt mongo schon für einige kleinere Logs, hatten noch keinen Cluster. Die Empfehlung ging auch von 10gen eindeutig in Richtung Sharding und jeden Shard in einem replicaSet zu Clustern. An sich ja eine gute Idee, allerdings nicht ganz seiteneffektfrei.

Wir sind ziemlich schnell drauf gekommen, dass Sharding auch overhead produziert - nicht zu knapp! So ist die Wahl des shard Key von essentieller Bedeutung beim Einsatz von Sharding. Der Einsatz von timestamps führte bei uns leider nicht zum gewünschten Effekt (das führt nämlich auch dazu, dass quasi minutenweise der Shard gewechselt wird, da nicht timestamp modulo Anzahl Maschinen benutzt wird um den Knoten rauszufinden, sondern immer 64K-Blöcke auf den selben Knoten landen). Außerdem war es so, dass requests auf eine geshardete Collection Extreme Last produziert hat. Teilweise wurden dadurch globale locks angelegt -> downtime!!!! Das war untragbar. Mal abgesehen davon, dass das setup extrem kompliziert ist (mongod für jeden Knoten, der daten hält, mit entsprechender Konfiguration, mongos auf jedem Knoten, der auf den shard zugreifen will und dann noch mongo-config-Server, die auch erreichbar sein müssen. Bei uns führte das zu mehr Problemen, als die, die es lösen sollte. So wurde immer mal wieder die Last auf den mongo Knoten extrem hoch nur durch irgendwelche Sharding- oder ReplicaSet-Sync-Operationen. Dummerweise haben die dazu geführt, dass alle anderen requests auch lahm wurden, teilweise so langsam, dass sie in Timeouts gelaufen sind.

Zu allem Überfluss lief auch eine ganze zeit lang der Failover nicht - zumindest nicht in Java. Wenn ein mongo Knoten ausgefallen ist, oder zur Wartung runtergefahren wurde, versuchte der Java Treiber dennoch darauf zuzugreifen.... Untragbar! Wir haben der mongo dennoch eine Chance gegeben, eine Menge von den learnings in Morphium einfließen lassen. Im Moment sind wir wieder auf dem Stand: Simplicity wins! Ein Cluster von 4 mongo Knoten, im replicaSet konfiguriert, kein Sharding. Und momentan keine Probleme!

Dabei ist auch wichtig, die Knoten korrekt zu dimensionieren. Wichtig sind: flotte Festplatte und genug RAM. Da ist man schnell etwas verwirrt, da der MongoD selbst nicht wirklich viel RAM nutzt, aber er nutzt "Memory Mapped files", d.h. der RAM-Bedarf des Systems steigt deutlich an. Wichtig ist, dass die Indices ins RAM passen. Sobald das nicht mehr der Fall ist, sollte man über Sharding noch mal nachdenken. Wir sind da aber noch weit von entfernt bei unseren Datenmengen.

En Problem mit den Datenmengen hatten wir dennoch: die eine der primären Datenbanken ist zwischenzeitlich auf eine Größe von mehr als 250GB im Filesystem angewachsen, obwohl reine Nutzdaten nur in etwa 100GB umfassten. Das liegt daran, wie mongo das Storage organisiert: einzelne Dateien werden angelegt, je nach dem, was gerade benötigt wird - aber nie gelöscht, selbst, wenn man die Collections entfernt. Der einmal belegt Platz wird wieder verwendet. Allerdings scheint es da zumindest bei unserem Setup ein Problem gegeben zuhaben, denn der Platzbedarf im Filesystem lag deutlich über den wirklichen Daten, obwohl nicht so viel gelöscht wurde... Die einzige Lösung für dieses Problem ist ein repairDatabase auszuführen. Das führt aber dazu, dass der entsprechende Knoten für die Dauer der Reparatur nicht erreichbar ist - Super, wenn auch der Failover nicht richtig funktioniert. Außerdem darf das Datenverzeichnis nur max. zu 50% belegt sein, will sagen: es muss noch genug Platz auf dem Filesystem sein, damit die Daten ein mal komplett umkopiert werden können.

Das konnten wir erst in letzter Zeit lösen, sind dann auch einen anderen Weg gegangen:

  • alle 2 Wochen wird ein Knoten im Cluster runtergefahren. Wenn es der Primary node war, wartet man zudem noch auf den Failover
  • auf diesem Knoten wird das Datenverzeichnis gelöscht
  • der Knoten wird im replicaset wieder hochgefahren
  • er synchronisiert alle Daten neu, minimaler platzbedarf auf der Platte.
Das funktioniert mittlerweile recht problemlos (der Failover funktioniert, und Morphium überwacht ja den Cluster Status). So haben wir den Platzbedarf auf den Platten um ca. 50% senken können.

Also als Tip: Jeder, der plant mongoDB einzusetzen, sollte auch diese Downtime für Repairdatabase oder das Syncen im Replicaset einplanen! Ich würde aus diesem Grund Mongo immer in einem ReplicaSet einsetzen!

Ein anderes Problem mit mongo hat zu grösseren hickups geführt, ein mal zu einem kompletten Datenbankcrash. Das muss man etwas ausführlicher erklären:

In mongo ist es möglich, komplett asynchron zu schreiben. D.h. Der schreibzugriff ist beendet, obwohl kein Knoten die Operation schon ausgeführt hat. Das ist natürlich etwas sub-optimal, wenn man diese Daten wieder einlesen möchte (z.B. Für Messaging oder Session Replikation). Deswegen ist es in mongo und in allen zugehörigen Treibern möglich, anzugeben,

  • darauf zu warten, dass der Knoten die Operation bekommen hat
  • ob darauf gewartet wird, dass die Schreiboperation auf der Platte persistiert wurde (fsync)
  • und auf wie viele Knoten man denn da warten will. Da kann man mittlerweile auch so was wie majority angeben.
  • beim lesen vomier mongo kann Management, welche Knoten im replicaSet benutzt werden sollen. Ob primärknoten, sekundärknoten oder irgendeiner.
Wir dachten natürlich, ok, bei wichtigen Daten warte auf alle Knoten. Das hat immer wird zu hängenden schreibprozessen und timeout exceptions in der Anwendung geführt. Auch nachklänge Diskussion mit 10Gen konnte mir keiner erklären, was da los ist. Die hi verwendete Option ist bestimmt, wie viele schreibvorgänge auf verschiedenen Knoten erfolgreich sein müssen. Wenn ich das auf die Anzahl der Knoten stelle, hängt der request (auch in der mongo Shell) reproduzierbar. Für alle Werte kleiner als die maximale Anzahl an Knoten funktioniert es.

in der Shell sieht dass dann so aus:

hi1:PRIMARY> db.testreplication.insert({_id:new Date()});printjson(db.runCommand({getLastError:1, w:4,  wtimeout:180000}));
{
        "n" : 0,
        "lastOp" : Timestamp(1347957897000, 17),
        "connectionId" : 431946,
        "wtimeout" : true,
        "waited" : 180000,
        "err" : "timeout",
        "ok" : 1
}

 

Das bedeutet, der Zugriff auf dieses Replicaset mit 4 Knoten (ohne Arbiter, eigentlich sind es 5) dauerte 180 Sekunden(!) in lief dann in einen Timeout. Sobald man irgend einen Wert kleiner 4 angibt, funktioniert es wunderbar. heute, 18.04.2013 ist dieser saublöde Fehler wieder aufgetreten, diesmal war allerdings nicht eingestellt, schreib auf alle Knoten, sondern nimm die Majority (also etwa die Hälfte). Sowas ist wirklich unnötig

Obwohl ich von 10Gen keine Bestätigung bekommen habe, denke ich, es werden bei diesen Schreibvorgängen, Hidden nodes (solche nodes, die nur die Daten bekommen, aber von denen nicht gelesen wird) dabei nicht mit gezählt werden. Ist nur eine Theorie, funktioniert aber.... Morphium unterstützt eine deklarative Festlegung sowohl der ReadPreference, also welche Cluster Knoten für den Lesezugriff erlaubt sind, als auch auf wie viele Knoten gewartet werden sollte bei Schreibzugriffen. Da das mit dem Schreiben auf alle knoten nicht funktioniert hat und die Anwendung immer wieder Dirty Reds bekommen hat (ich musste mir anhören, dass es ja keine Dirty Reds geben kann, da es das Konzept auf mongo gar nicht gibt - klugsch... Dann eben stale Data!) haben wir momentan alles auf primär umgestellt.. D.h. Wichtige Collections werden nur auf den primären Knoten geschrieben, und auch nur von dem gelesen... Widerspricht irgendwie dem lastverteilungsgedanken der replicasets.

Aber ok... Momentan haben wir noch keine Probleme mit der Last, sollte das kommen, muss sich 10Gen da was einfallen lassen.... Der Bug ist übrigens auch in der aktuellen V2.4.1 von mongodb vorhanden... (wenn man von einem Bug reden kann, evtl. ist es nur ein Fehler in der Doku)

Fazit: Nichts desto Troz kann ich MongoDb weiterempfehlen, wenn auch nicht uneingeschränkt. Ich habe Mongo jetzt mit Hilfe von Morphium auch in anderen Projekten eingesetzt (z.B. CalugaMed). Wenn man weiß, was man tut, kann mongo wirklich einen Großen Vorteil bringen. Da man nicht so fix an Strukturen gebunden ist, wie in einer Relationalen Datenbank, hat man viel mehr Möglichkeiten. Als "Datenmülleimer" ungeschlagen! Allerdings kann man auf Grund des fehlenden Zwangs zur Struktur auch mehr falsch machen! Datenmigrationen sind relativ leicht zu machen, die Anfragesprache (JavaScript) ist gut und mächtig (auch wenn immernoch ein brauchbares FrontEnd fehlt) und mit Morphium gibt es auch ein recht gutes Mapping-Framework (ich weiß, Eigenlob stinkt ;-) ).

Dennoch würde ich Mongo nicht für alles und immer benutzen. Man muss sich überlegen, was man benötigt. Wenn man z.B. Joins benötigt bzw. die Daten eben nicht so document based ablegen kann, so sind diese Anfragen in Mongo eher schwierig bis unmöglich abzubilden. Abhilfe schaft da evtl. auch der "Mut zur Redundanz". D.h. evtl. daten mehrfach ablegen. Komplexe Anfragen funktionieren zwar, können aber zu echten Problemen und Downtimes führen, wenn man nicht aufpasst (Indizes!!! Indizes!!! Indizes!!!).

Alles in Allem ist Mongo eine echte Alternative zu SQL-Datenbanken und kann in bestimmten Fällen echte Performance und flexibilitätsvorteile bringen. Einen Blick ist es auf jeden Fall wert.

mongo gibt es hier: www.mongodb.org

Morphium Projektseite: code.google.com/p/morphium

Morphium deutsch: www.caluga.de/morphium.html


Kategorie: Allgemeines

Neue Version von #Morphium

So, 17. 06. 2012 - Tags: java morphium mongodb

Neue Version von Morphium (#MongoDb Object Mapper) V1.3.23 und

MorphiumGUI V0.6.8 - http://t.co/YRIVAUgL


Kategorie: Computer

Morphum Object Mapper für MongoDB (noSQL DB)

Di, 03. 04. 2012 - Tags:

In der Arbeit bei holidayinsider.com nutzen wir sehr exzessiv mongoDB für die Datenhaltung. Das ist vor allem dem Umstand geschuldet, dass unsere Daten eher "flach" sind und wir keine komplexen Abhängigkeiten abbilden müssen. Wichtig war uns, eine möglichst flexible und vor allem Performance Implementierung zu realisieren.

Dazu haben wir zunächst direkt auf den Java-Treiber vom MongoDB-Team. Das hat gut funktioniert und hat auch viel Performance gebracht.

Allerdings wollten wir aus gründen der Wartbarkeit natürlich auch eine Kapsel um den Zugriff auf Mongo bauen, die uns vor allem das Object-Mapping abnehmen sollte (ähnlich Hibernate). Des weiteren ist für eine solch hoch Performance Seite unbedingt wichtig, dass wir MongoDB nicht zu sehr belasten, d.h. Anfragen nach Möglichkeit cachen.

Es gibt da momentan nur wenige Projekte, die das für Java machen, und keines von denen Unterstützt Caches in irgendeiner form. Wir mussten also selbst ran ;-)

Eine etwas erweiterte und weniger spezialisierte Form der oben Beschriebenen Kapsel für Mongo habe ich jetzt unter code.google.com/p/morphium zum Download frei gegeben. Der Name ist mit Absicht ähnlich dem Morphia-Projekt ;-) Aber im Gegensatz zu Morphia unterstützt Morphium eine Menge zusätzlicher Features:

  1. Caches - wird nur mit Annotations eingestellt, Morphium macht den Rest
  2. GUI Tools - Es gibt eine von JTable abgeleitete Klasse zur Darstellung, zum suchen und editieren von mongoDB-Objekten
  3. Security (unfertig) - ist noch nicht 100% implementiert, aber es gibt einen Security-Manager, über den der Zugriff auf einzelne Entitäten geregelt werden kann.
  4. Flexible Architektur: man kann sowohl das SuchObjekt (query) als auch den Object-Mapper selbst durch einen eigenen Ersetzen, wenn man möchte.
  5. und was uns noch so alles einfällt ;-)
Aktuell ist Morphium in der Version 0.5 verfügbar - die ist Stabil und das Mapping funktioniert 100% (mit Junit-Tests geprüft). Es fehlt vor allem noch ein SecurityManager der etwas mehr macht als nur "OK" zu sagen und einige Verbesserungen an den GUI tools. Es wird stetig weiter entwickelt (wenn auch momentan nur von mir - Hilfe ist willkommen ;-)), da ich noch einige Projekte in der Queue habe, bei denen ich Morphium einsetzen muss/werde. Des weiteren fehlen noch "Write-Concern" Einstellungen in der Entity-Annotation und das festlegen von Indices (kommt in V0.6, die Gui Verbesserungen danach)...

Noch eines ist wichtig; im Gegensatz zu anderen Mappern ist es bei Morphium nicht notwendig, die zu Mamppenden Klassen / Pakete vorher bekannt zu machen - das erfolgt lazy (ebenso die Index-Generierung etc).

Schaut es euch mal an, es ist schon recht weit fortgeschritten (auch wenn 0.5 noch eine ziemlich kleine Versionsnummer ist)...

Viel Spass beim rumprobieren...


Kategorie: Tweet --> Allgemeines

New Java Object-Mapper for #Mongodb

Mo, 02. 04. 2012 - Tags: tweet

New Java Object-Mapper for #MongoDb called Morphium. Have a look: http://t.co/YVNi1ILs


Kategorie: Allgemeines --> Tweet

Saying that Java is nice...

Mi, 21. 09. 2011 - Tags: tweet java

Ursprünglich veröffentlicht auf: https://boesebeck.name

“Saying that Java is nice because it works on all OSes is like saying that anal sex is nice because it works on all genders.” @NIxiePixel

Suchergebnis: 62

<< 1 ... 2 ... 3 ... >>