PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/README.md

https://github.com/instaclick/RiakBundle
Markdown | 512 lines | 421 code | 91 blank | 0 comment | 0 complexity | fdb8cd711b6fdf3d75a969cf68a8f987 MD5 | raw file
  1. [![Build Status](https://travis-ci.org/remialvado/RiakBundle.png?branch=master)](https://travis-ci.org/remialvado/RiakBundle)
  2. Table Of Content
  3. ================
  4. - [Features](#features)
  5. - [Roadmap](#roadmap)
  6. - [Dependencies](#dependencies)
  7. - [Installation](#installation)
  8. - [Configuration](#configuration)
  9. - [Basic Usage](#basic-usage)
  10. - [Accessing a Cluster](#accessing-a-cluster)
  11. - [Defining Bucket content](#defining-bucket-content)
  12. - [Insert or Update Data into a Bucket](#insert-or-update-data-into-a-bucket)
  13. - [Fetch Data from a Bucket](#fetch-data-from-a-bucket)
  14. - [Delete Data from a Bucket](#delete-data-from-a-bucket)
  15. - [List keys inside a Bucket](#list-keys-inside-a-bucket)
  16. - [Count keys inside a Bucket](#count-keys-inside-a-bucket)
  17. - [Search items inside a Bucket using Riak Search](#search-items-inside-a-bucket-using-riak-search)
  18. - [Perform MapReduce request on a Cluster](#perform-mapreduce-request-on-a-cluster)
  19. - [Extended Usage](#extended-usage)
  20. - [Load and edit Bucket configuration](#load-and-edit-bucket-configuration)
  21. - [Enable Automatic Indexation](#enable-automatic-indexation)
  22. - [Play with Headers](#play-with-headers)
  23. - [Define your own bucket class](#define-your-own-bucket-class)
  24. - [Admin Tasks](#admin-tasks)
  25. - [Common Options](#common-options)
  26. - [List existing buckets](#list-existing-buckets)
  27. - [Delete all buckets](#delete-all-buckets)
  28. - [List keys inside a bucket](#list-keys-inside-a-bucket)
  29. - [Count keys inside a bucket](#count-keys-inside-a-bucket)
  30. - [Delete all keys inside a bucket](#delete-all-keys-inside-a-bucket)
  31. - [Delete one key inside a bucket](#delete-one-key-inside-a-bucket)
  32. Features
  33. --------
  34. RiakBundle is designed to ease interaction with [Riak](http://basho.com/products/riak-overview/) database. It allows developers to focus on
  35. stored objects instead of on communication APIs. Supported features are :
  36. - support for multi-clustering
  37. - configure clusters : protocol, domain, port, maximum supported connections in parallel, buckets list, ...
  38. - allow developers to plus directly some code into Guzzle to : use proxy, log or count all calls, ...
  39. - insert, delete and fetch objects into a bucket with support for parallel calls (curl_multi)
  40. - fetch, edit and save bucket properties
  41. - list and count keys inside a bucket
  42. - search on a bucket
  43. - perform mapreduce operations through a fluid interface
  44. - console tasks to list and count keys inside a bucket, delete a single key or an entire bucket
  45. - support for custom search parameters (used by custom Riak Search builds)
  46. - support for Bucket inheritance to allow you to define custom services on a bucket
  47. Roadmap
  48. -------
  49. - support for Secondary indexes insert and fetch operations
  50. - support for extended bucket settings (n_val, ...) in YAML configuration
  51. - performance dashboard added to Symfony Debug Toolbar
  52. Dependencies
  53. ------------
  54. RiakBundles requires some other bundles / libraries to work :
  55. - Guzzle : to handle HTTP requests thru curl
  56. - JMSSerializer : to handle serialization operations
  57. Installation
  58. ------------
  59. Only installation using composer is supported at the moment but source code could be easily tweaked to be used outside it.
  60. In order to install RiakBundle in a classic symfony project, just update your composer.json and add or edit this lines :
  61. ```javascript
  62. "kbrw/riak-bundle": "1.0.*"
  63. ```
  64. You may also need to adjust the "minimum-stability" to "dev" in your composer.json to make this work.
  65. Then, you just need to add some bundle into your app/AppKernel.php :
  66. ```php
  67. new JMS\SerializerBundle\JMSSerializerBundle(),
  68. new Kbrw\RiakBundle\RiakBundle(),
  69. ```
  70. Configuration
  71. -------------
  72. For the next sections, let's assume your infrastructure is made of 2 Riak clusters : one used to store your backend objects and one to store all logs.
  73. The backend cluster contains two pre-defined buckets. One to store some users in JSON and one to store some cities in XML.
  74. On the log cluster, you create a new bucket every day(I know it's strange but why not...) so you can't pre-configured buckets in this cluster.
  75. Moreover, you have conducted robustness campaigns and you know that backend cluster is able to support up to 500 parallels connection where your Frontend is never hitted by more than 5 connections in parallels. So you know that you can send 100 backend requests in parallels per frontend requests but no more. If you try to store or fetch or delete more than 100 objects at once, only the first 100 will be processed. Others will have to wait for a slot to opened. RiakBundle will handle this slot mecanism for you.
  76. All configuration can be made on config.yml file under a new ```riak``` namespace. With that in mind, you should define the following configuration :
  77. Example :
  78. ```yaml
  79. riak:
  80. clusters:
  81. backend:
  82. domain: "127.0.0.1"
  83. port: "8098"
  84. client_id: "frontend"
  85. max_parallel_calls: 100
  86. buckets:
  87. users:
  88. fqcn: 'MyCompany\MyBundle\Model\User'
  89. format: json
  90. cities:
  91. fqcn: 'MyCompany\MyBundle\Model\City'
  92. format: xml
  93. log:
  94. domain: "127.0.0.1"
  95. port: "8099"
  96. client_id: "frontend"
  97. ```
  98. Basic Usage
  99. -----------
  100. ### Accessing a cluster
  101. Each cluster becomes a service called "riak.cluster.<clusterName>". In our example, you can access your two clusters using :
  102. ```php
  103. $backendCluster = $container->get("riak.cluster.backend");
  104. $logCluster = $container->get("riak.cluster.log");
  105. ```
  106. ### Defining bucket content
  107. Riak stores text-based objects so you need to provide a text-based version of your objects. The best practice we recommand is to create an annotated object which represent your model.
  108. For example, an User class could be implemented this way :
  109. ```php
  110. <?php
  111. namespace MyCompany\MyBundle\Model;
  112. use JMS\Serializer\Annotation as Ser;
  113. /**
  114. * @Ser\AccessType("public_method")
  115. * @Ser\XmlRoot("user")
  116. */
  117. class User
  118. {
  119. /**
  120. * @Ser\Type("string")
  121. * @Ser\SerializedName("id")
  122. * @var string
  123. */
  124. protected $id;
  125. /**
  126. * @Ser\Type("string")
  127. * @Ser\SerializedName("email")
  128. * @var string
  129. */
  130. protected $email;
  131. function __construct($id = null, $email = null)
  132. {
  133. $this->setId($id);
  134. $this->setEmail($email);
  135. }
  136. public function getId()
  137. {
  138. return $this->id;
  139. }
  140. public function setId($id)
  141. {
  142. $this->id = $id;
  143. }
  144. public function getEmail()
  145. {
  146. return $this->email;
  147. }
  148. public function setEmail($email)
  149. {
  150. $this->email = $email;
  151. }
  152. }
  153. ```
  154. RiakBundle can automatically take care of serialization / deserialization so that you will always work with User instance and never with its text-based (JSON, XML or YAML) reprensentation.
  155. Your model class and the serialization method can be defined into configuration like described above or you can specify it on a bucket instance like this :
  156. ```php
  157. $logCluster = $container->get("riak.cluster.log");
  158. $bucket = $logCluster->getBucket("log_" . date('Y-m-d'));
  159. $bucket->setFullyQualifiedClassName("MyCompany\MyBundle\Model\LogEntry");
  160. $bucket->setFormat("json");
  161. ```
  162. ### Insert or update data into a bucket
  163. Once your bucket is configured, you just have to create an instance of the object you want to store and ask RiakBundle to store it for you. Example :
  164. ```php
  165. $backendCluster = $container->get("riak.cluster.backend");
  166. $user = new \MyCompany\MyBundle\Model\User("remi", "remi.alvado@yahoo.fr");
  167. $backendCluster->getBucket("user")->put(array("remi" => $user));
  168. ```
  169. You can even write multiple users at the same time using parallel calls to Riak :
  170. ```php
  171. $backendCluster = $container->get("riak.cluster.backend");
  172. $grenoble = new \MyCompany\MyBundle\Model\City("38000", "Grenoble");
  173. $paris = new \MyCompany\MyBundle\Model\City("75000", "Paris");
  174. $backendCluster->getBucket("city")->put(
  175. array(
  176. "38000" => $grenoble,
  177. "75000" => $paris
  178. )
  179. );
  180. ```
  181. The same mecanism can be used to update a data :
  182. ```php
  183. $backendCluster = $container->get("riak.cluster.backend");
  184. $paris = $backendCluster->getBucket("city")->uniq("paris");
  185. $paris->setName("paris intra muros");
  186. $backendCluster->getBucket("city")->put(array("75000" => $paris));
  187. ```
  188. ### Fetch data from a bucket
  189. Once you have some data into your bucket, you can start fetching them. As a matter of fact, you can even have no data and start fetching :)
  190. The Bucket class provides two ways to fetch data depending on your needs :
  191. - get one single data with the ```uniq($key)``` function
  192. - get a list of data with the ```fetch($keys)``` function
  193. Internally, this two methods are doing the same thing but "uniq" will provide you a [\Kbrw\RiakBundle\Model\KV\Data](RiakBundle/blob/master/Model/KV/Data.php) instance while "fetch" will provide you a [\Kbrw\RiakBundle\Model\KV\Datas](RiakBundle/blob/master/Model/KV/Datas.php) one.
  194. [\Kbrw\RiakBundle\Model\KV\Data](RiakBundle/blob/master/Model/KV/Data.php) class lets you access the actual object (User, City, ...) but also the Response headers like VClock, Last-Modified, ...
  195. Example :
  196. ```php
  197. // Using fetch to get multiple objects
  198. $backendCluster = $container->get("riak.cluster.backend");
  199. $datas = $backendCluster->getBucket("city")->fetch(array("paris", "grenoble"));
  200. $cities = $datas->getStructuredObjects(); // $cities will be an array of \MyCompany\MyBundle\Model\City instances
  201. // Using fetch to get one object
  202. $backendCluster = $container->get("riak.cluster.backend");
  203. $datas = $backendCluster->getBucket("city")->fetch(array("paris"));
  204. $city = $datas->first()->getStructuredContent(); // $city will be a \MyCompany\MyBundle\Model\City instance
  205. // Using uniq to get one object
  206. $backendCluster = $container->get("riak.cluster.backend");
  207. $city = $backendCluster->getBucket("city")->uniq("paris"); // $city will be a \MyCompany\MyBundle\Model\City instance
  208. ```
  209. ### Delete data from a bucket
  210. You just have to supply a list of keys that have to been deleted.
  211. Example :
  212. ```php
  213. // delete a list of key/value pairs
  214. $backendCluster = $container->get("riak.cluster.backend");
  215. $backendCluster->getBucket("city")->delete(array("paris", "grenoble"));
  216. // delete one single key/value pair
  217. $backendCluster = $container->get("riak.cluster.backend");
  218. $backendCluster->getBucket("city")->delete("paris");
  219. ```
  220. ### List keys inside a bucket
  221. Riak does not provide an easy way like SQL databases to list all keys inside a bucket. To do so, it has to run over all keys in the cluster. So, this operation can be really long on a big cluster even if you query against a very small bucket.
  222. Moreover, even if RiakBundle uses the "keys=stream" parameter to stream keys from Riak instead of asking Riak to return them all in one response, please keep in mind that PHP might not work well with a multi-million values array.
  223. To display all keys inside a bucket :
  224. ```php
  225. // delete a list of key/value pairs
  226. $backendCluster = $container->get("riak.cluster.backend");
  227. foreach($backendCluster->getBucket("city")->keys() as $key) {
  228. echo "$key\n";
  229. }
  230. ```
  231. ### Count keys inside a bucket
  232. Sometimes, there are too many keys inside a bucket to get them but you might want to count them.
  233. To count all keys inside a bucket :
  234. ```php
  235. // delete a list of key/value pairs
  236. $backendCluster = $container->get("riak.cluster.backend");
  237. echo "'city' bucket contains" . $backendCluster->getBucket("city")->count() . " key(s)."
  238. ```
  239. ### List buckets inside a cluster
  240. If you need to list all buckets inside a cluster, you can use the following example :
  241. ```php
  242. $backendCluster = $container->get("riak.cluster.backend");
  243. print_r(backendCluster->bucketNames());
  244. ```
  245. ### Search items inside a bucket using Riak Search
  246. Riak supports a Solr-like (and -light) search engine. RiakBundle lets you searching for items on every buckets with search feature activated.
  247. Search can be executed with a simple Solr-Like string query or with a fully qualified [\Kbrw\RiakBundle\Model\Search\Query](RiakBundle/blob/master/Model/Search/Query.php) instance.
  248. Examples :
  249. ```php
  250. // with string based query
  251. $backendCluster = $container->get("riak.cluster.backend");
  252. $usersBucket = $backendCluster->getBucket("users");
  253. $response = $usersBucket->search("id:rem*");
  254. // with full Query instance
  255. $backendCluster = $container->get("riak.cluster.backend");
  256. $usersBucket = $backendCluster->getBucket("users");
  257. $query = new \Kbrw\RiakBundle\Model\Search\Query("id:rem*");
  258. $query->addFieldInList("id"); // only look in "id" field for each object stored in the bucket
  259. $query->setRows(5); // return only 5 results
  260. $response = $usersBucket->search($query);
  261. ```
  262. ### Perform MapReduce request on a cluster
  263. Riak allows developers to execute mapreduce operations an entire cluster. RiakBundle offers the same possibility through a fluent interface.
  264. Mapreduce operations are made of two main parts :
  265. - select keys the map phase will be executed against. Developers can choose to execute the mapreduce operation against a full bucket, a predefined subset of keys or a filterable set of keys. The three possibilities will be described below.
  266. - define map and reduce phases using javascript functions, erlang module, ... Some possibilities will be defined below but you can dive into the source code or the API to find more.
  267. Generic example :
  268. ```php
  269. $result = $this->cluster->mapReduce()
  270. ->on("meals")
  271. ->map('function(riakObject) {...}')
  272. ->link('meals', 'menu_') // to follow links pointing to any key matching "menu*" on "meals" bucket
  273. ->reduce('function(riakObject) {...}')
  274. ->responseShouldBe("\Some\JMS\Serializable\Type")
  275. ->send();
  276. ```
  277. Basic example :
  278. ```php
  279. // count how many times 'pizza' are served, meal by meal on all seasons
  280. $bucket = $this->cluster->getBucket("meals", true);
  281. $bucket->delete($bucket->keys());
  282. $bucket->put(array("summer-1" => "pizza salad pasta meat sushi"));
  283. $bucket->put(array("summer-2" => "pizza pizza pizza pizza pizza"));
  284. $bucket->put(array("winter-1" => "cheese cheese patatoes meat vegetables"));
  285. $bucket->put(array("autumn-1" => "pizza pizza pizza mushroom meat"));
  286. $result = $this->cluster->mapReduce()
  287. ->on("meals")
  288. ->map('
  289. function(riakObject) {
  290. var m = riakObject.values[0].data.match("pizza");
  291. return [[riakObject.key, (m ? m.length : 0 )]];
  292. }
  293. ')
  294. ->responseShouldBe("array<string, string>")
  295. ->send();
  296. ```
  297. Key filter example :
  298. ```php
  299. // count how many times 'pizza' are served, meal by meal only on winter and autumn
  300. // Apply a specific timeout (10sec) as well
  301. $result = $this->cluster->mapReduce()
  302. ->filter("meals")
  303. ->tokenize("-", 1)
  304. ->or()
  305. ->eq("winter")
  306. ->eq("autumn")
  307. ->end()
  308. ->done()
  309. ->map('
  310. function(riakObject) {
  311. var m = riakObject.values[0].data.match("pizza");
  312. return [[riakObject.key, (m ? m.length : 0 )]];
  313. }
  314. ')
  315. ->timeout(10000)
  316. ->responseShouldBe("array<string, string>")
  317. ->send();
  318. ```
  319. Key filter example :
  320. ```php
  321. // use an erlang function to execute something on meals
  322. $result = $this->cluster->mapReduce()
  323. ->on("meals")
  324. ->configureMapPhase()
  325. ->setLanguage("erlang")
  326. ->setModule("riak_mapreduce")
  327. ->setFunction("map_object_value")
  328. ->done()
  329. ->responseShouldBe("array<Acme\DemoBundle\Model\Meal>")
  330. ->send();
  331. ```
  332. Advanced Usage
  333. --------------
  334. ### Load and Edit Bucket configuration
  335. Riak allows developers to customize some bucket configuration like _nval_, the number of replicas to be store in the cluster for each and every data.
  336. Please have a look at Riak documentation for closer details.
  337. Using RiakBundle, you can easily update bucket configuration. The [\Kbrw\RiakBundle\Model\Bucket\Bucket](RiakBundle/blob/master/Model/Bucket/Bucket.php) class is not only the place used to execute operations on a bucket, it also contains bucket properties that you can manage.
  338. Example :
  339. ```php
  340. $backendCluster = $container->get("riak.cluster.backend");
  341. $users = $backendCluster->addBucket("users", true); // the second parameter will force RiakBundle to fetch properties for this bucket
  342. $users->getProps()->setNVal(5);
  343. $users->save();
  344. ```
  345. ### Enable automatic indexation
  346. Riak supports automatic indexation of JSON / XML / Erlang datas from Riak KV to Riak Search. This feature needs to be activated on a per-bucket level. RiakBundle lets you easily do that :
  347. Example :
  348. ```php
  349. $backendCluster = $container->get("riak.cluster.backend");
  350. $usersBucket = $backendCluster->getBucket("users");
  351. $usersBucket->enableSearchIndexing();
  352. $usersBucket->save();
  353. ```
  354. ### Play with headers
  355. Riak does not only associate an object to a key but also some headers. Some are pre-defined (Last-Modified, X-Riak-Vclock, ...) but you can also put your own custom headers.
  356. As explained above, the ```put($objects)``` method takes an associated array of objects. Thoses objects can either be your own simple representation of the data you want to store in Riak or a [\Kbrw\RiakBundle\Model\KV\Data](RiakBundle/blob/master/Model/KV/Data.php) instance (the same one returned by the fetch and uniq methods).
  357. On this object, you can define your own custom headers by using the headerBag property which is a [\Symfony\Component\HttpFoundation\HeaderBag](../symfony/symfony/blob/master/src/Symfony/Component/HttpFoundation/HeaderBag.php).
  358. Example :
  359. ```php
  360. $remi = new \MyCompany\MyBundle\Model\User("remi", "remi.alvado@yahoo.fr");
  361. $data = new \Kbrw\RiakBundle\Model\KV\Data("remi");
  362. $data->setContent($remi);
  363. $data->getHeaderBag()->setHeader("X-Signup-Date", date('Y-m-d'));
  364. $backendCluster = $container->get("riak.cluster.backend");
  365. $backendCluster->getBucket("users")->put($remi);
  366. ```
  367. The same mecanism can be used to handle Riak merge issues with the X-Riak-Vclock header.
  368. ### Define your own Bucket class
  369. Sometimes, you might want to define you own Bucket class so that you can override some existing methods, add new ones, ... This can easily be done by hacking the condiguration like that :
  370. ```yaml
  371. riak:
  372. clusters:
  373. backend:
  374. domain: "127.0.0.1"
  375. port: "8098"
  376. client_id: "frontend"
  377. max_parallel_calls: 100
  378. buckets:
  379. points_of_interests:
  380. fqcn: 'MyCompany\MyBundle\Model\PointOfInterest'
  381. format: json
  382. class: 'Acme\DemoBundle\Model\AcmeBucket'
  383. ```
  384. With this configuration, the "points_of_interests" bucket will be initialized as a ```Acme\DemoBundle\Model\AcmeBucket``` and not a regular Bucket. You can now implement this class (that you extends ```\Kbrw\RiakBundle\Model\Bucket\Bucket```) to atch your requirements.
  385. In the same spirit, each time a Bucket is added to a Cluster, an event named "riak.bucket.add" is thrown to the EventDispatcher. This ```\Symfony\Component\EventDispatcher\GenericEvent``` contains the newly created bucket that you can manipulate the way you want.
  386. For closer details, you can have a look at [this example](https://gist.github.com/4363553).
  387. Admin Tasks
  388. -----------
  389. ### Common Options
  390. All tasks working on a cluster (so... all tasks) support the following option :
  391. - ```-c``` or ```--cluster``` : the cluster name. If not provided, it will be asked on the command line
  392. All tasks working on a bucket support the following option :
  393. - ```-b``` or ```--bucket``` : the bucket name. If not provided, it will be asked on the command line
  394. All tasks which list or count items support the following option :
  395. - ```-r``` or ```--raw``` : if provided, a raw output format will be used. Mainly used for bash scripting.
  396. All tasks which delete items support the following option :
  397. - ```-y``` or ```--yes``` : if provided, all yes/no questions will be skipped. mainly used for bash scripting.
  398. ### List existing buckets
  399. ```bash
  400. php app/console riak:cluster:list -c backend
  401. ```
  402. ### Delete all buckets
  403. ```bash
  404. php app/console riak:cluster:deleteAll -c backend
  405. ```
  406. ### List keys inside a bucket
  407. ```bash
  408. php app/console riak:bucket:list -c backend -b meals
  409. ```
  410. ### Count keys inside a bucket
  411. ```bash
  412. php app/console riak:bucket:count -c backend -b meals
  413. ```
  414. ### Delete all keys inside a bucket
  415. ```bash
  416. php app/console riak:bucket:deleteAll -c backend -b meals
  417. ```
  418. ### Delete one key inside a bucket
  419. ```bash
  420. php app/console riak:bucket:delete -c backend -b meals -k summer-2
  421. ```