{"id":893,"date":"2014-04-14T18:11:18","date_gmt":"2014-04-14T22:11:18","guid":{"rendered":"http:\/\/blog.agilityfeat.com\/?p=893"},"modified":"2020-11-10T20:46:32","modified_gmt":"2020-11-10T20:46:32","slug":"managing-data-in-hbase-using-ruby-and-thrift","status":"publish","type":"post","link":"https:\/\/agilityfeatpanama.com\/en\/blog\/2014\/04\/managing-data-in-hbase-using-ruby-and-thrift\/","title":{"rendered":"Managing data in HBase using Ruby and Thrift"},"content":{"rendered":"<p>AgilityFeat is notable for attracting clients who are looking into working with cutting-edge technologies, and thanks to that, recently, I&#8217;ve had the amazing opportunity to dive in head-first into researching about and experimenting with various BigData technologies, focusing mainly on Hadoop.<\/p>\n<p>Hadoop is still at a very early adoption stage, which has proven to be not only challenging, but inspiring as well.<\/p>\n<p>I&#8217;ve been able to contribute to the Ruby\/Hadoop community by coming up with a SQL to HBase parser called HipsterSqlToHbase, and thanks to this and other similar projects I&#8217;ve worked on, my knowledge about data processing with Hadoop has been growing exponentially.<\/p>\n<p>I could write volumes about Hadoop and the amazing toolset that has been evolving within its ecosystem, but to keep things concise, this post will be, in a sense, a continuation from my <strong><a href=\"http:\/\/www.jlescure.com\/blog\/hadoop-for-rubyists-quick-intro-to-hbase\/\">Introduction to Hadoop for Rubyists<\/a><\/strong> (in which I go into great depth of detail as to how quickly you can begin using Hadoop with Ruby working with nothing but HBase and Thrift).<\/p>\n<p>In the next few lines I&#8217;ll share the intricacies of HBase data structures, show you how to insert and update simple and complex rows of data from Ruby into HBase (using Thrift) and finally how to harness the power of HBase&#8217;s Filtering Language to retrieve and bend data to your heart&#8217;s content.<\/p>\n<h2>How HBase structures its data<\/h2>\n<p>In my introductory post about Hadoop I mention how HBase saves all data directly to HDFS (as opposed to Hive, Pig, or Mahout which all generate MapReduce jobs which need to be processed before any data is stored and actually becomes retrievable). HBase is able to achieve this through it&#8217;s data structure which relies heavily on keeping every change to each row stored separately, and atomically:<\/p>\n<p><a href=\"\/wp-content\/uploads\/2014\/04\/hbase_data_structure_chart.jpg\"><img loading=\"lazy\" class=\"aligncenter size-full wp-image-894\" alt=\"hadoop, agilityfeat, ruby, hbase\" src=\"\/wp-content\/uploads\/2014\/04\/hbase_data_structure_chart.jpg\" width=\"640\" height=\"520\" style=\"max-width: 640px; max-height: 520px;\" \/><\/a><br \/>\n<br clear=\"left\"\/><br \/>\nAs you can tell by the previous chart, structurally speaking, every column in an HBase row is stored separately as a key-value pair. In this case the row id is responsible for linking the columns together, along with a timestamp. The reason a timestamp is added is so that you can store multiple versions of a row (up to 5 by default, although this setting is completely customizable).<\/p>\n<p>To explain further about how HBase stores data in HDFS would be an overkill for this post, so I&#8217;d suggest if you want to delve deeper into the subject later on, to check out this <strong><a href=\"http:\/\/www.slideshare.net\/enissoz\/hbase-and-hdfs-understanding-filesystem-usage\">slideshare<\/a><\/strong> by Enis Soztutar from Apache.<br \/>\n<br clear=\"left\"\/><\/p>\n<h2>Mutating an HBase Table (Better than \u00abInserting\u00bb and \u00abUpdating\u00bb Rows)<\/h2>\n<p>Now that you manage a basic concept of how data is stored in HBase, let&#8217;s show you how to manipulate these datasets.<\/p>\n<p>In my <strong><a href=\"http:\/\/www.jlescure.com\/blog\/hadoop-for-rubyists-quick-intro-to-hbase\/\">Introduction to Hadoop for Rubyists<\/a><\/strong> I had already outlined how to create a table and how to \u00abinsert\u00bb data into said table.<\/p>\n<p>I am \u00abair quoting\u00bb the word \u00abinsert\u00bb here, because in HBase you do not insert nor update.<\/p>\n<p>In regular SQL databases, tables are structures which contain rows, and these rows are inserted (created), updated (edited), or deleted. Hbase tables, on the other hand, contain no rows; they are actually made up of atomic values which are linked, grouped together, and displayed as rows. Also, within HDFS, data is never \u00abmodified\u00bb (and I do mean <strong>never<\/strong>). Whenever you \u00abmodify\u00bb a value, this simply generates a new key-pair file within the table, which means that the original value which you&#8217;re modifying is never touched; it is only ignored from that point on (unless you specify otherwise). This is why any data manipulation within an HBase table is referred to as a <strong>mutation<\/strong> of said table.<\/p>\n<p>Alright, enough chit chat, let&#8217;s get down to business.<\/p>\n<p>If you haven&#8217;t already, download the test files used in my intro to HBase blog post by clicking <strong><a href=\"http:\/\/www.jlescure.com\/blog\/attachments\/hbase-ruby-test-files.zip\">here<\/a><\/strong>.<\/p>\n<p>You&#8217;re going to be working mainly with the file named <strong>hbase-put-row.rb<\/strong>, focusing on the following lines of code:<\/p>\n<p><script src=\"https:\/\/gist.github.com\/asime\/10872481.js\"><\/script><\/p>\n<p>This is the main structure of the code you will be using to mutate your database from here on. You might&#8217;ve noticed that the code in of itself is pretty simple, but it can get messy quickly once you start performing more complex mutations; especially if you&#8217;re not sure of what you&#8217;re doing.<\/p>\n<p>So let&#8217;s break it down part by part.<\/p>\n<p>The first line you&#8217;re interested in is:<\/p>\n<p><script src=\"https:\/\/gist.github.com\/asime\/10986032.js\"><\/script><\/p>\n<p>All you&#8217;re doing here is defining a variable\u00a0<strong>thrift_mutations<\/strong> which will be an array. It might be simple, but this is already telling us about Thrift&#8217;s capabilities for running more than one mutation on a single call. This feature is super useful, and it&#8217;s one of the cornerstones of the code I used to forge the<strong>\u00a0<a href=\"https:\/\/github.com\/jeanlescure\/hipster_sql_to_hbase\">HipsterSqlToHbase gem<\/a><\/strong>.<\/p>\n<p>Let&#8217;s move on to the next line:<\/p>\n<p><script src=\"https:\/\/gist.github.com\/asime\/10986083.js\"><\/script><\/p>\n<p>Now we&#8217;re talking. This is definitely an interesting piece of code. Here you&#8217;re running the <strong>new<\/strong> method from the\u00a0<strong>HBase::Mutation<\/strong> class and passing it two named arguments. This will return an\u00a0<strong>HBase::Mutation<\/strong> instance which you&#8217;ll push into our previously defined array.<\/p>\n<p>Let&#8217;s talk a little more about the <strong>HBase::Mutation<\/strong> class.<\/p>\n<p>By definition a mutation changes a table, but within this class&#8217;s code&#8217;s semantics, it focuses on allowing us to change column values. Hence the reason you&#8217;re passing it a column&#8217;s name and the value you want to place on this column.<\/p>\n<p>Now that you&#8217;ve specified the value that you want to store, you need to tell HBase where to apply this change:<\/p>\n<p><script src=\"https:\/\/gist.github.com\/asime\/10986145.js\"><\/script><\/p>\n<p>Again, the wording is somewhat misleading since you&#8217;re running a method called\u00a0<strong>mutateRow<\/strong> to apply our change. This method accepts four arguments: the name of the table you&#8217;re mutating, the id of the row that you are going to be adding the data into, an array of valid <strong>HBase::Mutation<\/strong> instances, and finally a hash which can contain extra options.<\/p>\n<p>I do feel the need to be emphatic about this issue, again. You are\u00a0<strong>NOT<\/strong>going to be mutating, creating nor modifying the row. Once you run the previous line of code, within HDFS, inside a folder assigned to the table data by HBase, a file will be generated which contains nothing more than the column name and the value it has been assigned, and the file itself will be named using the row id and a timestamp.<\/p>\n<p>And, just to go a little further into this matter, once you run this code, If you take note of the UUID that was used as the row id and replace the\u00a0<strong>SecureRandom.uuid<\/strong> portion of the code with it, the next time you run the code you will be performing an \u00abupdate\u00bb instead of an \u00abinsert\u00bb, except it won&#8217;t truly be an \u00abupdate\u00bb since a new file will still be generated to store the \u00abmodified\u00bb data and the previous file will simply be ignored. Both of these files will have the same row id, but a different timestamp.<\/p>\n<p>Now let&#8217;s make this code a little more real world friendly.<\/p>\n<p>Let us assume you&#8217;ve got a table for storing users and their info (the table columns would be &#8216;user_name&#8217;, &#8216;full_name&#8217;, and &#8216;password&#8217;), and you want to be able to save multiple users at a time.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/asime\/10873657.js\"><\/script><\/p>\n<p>Again, let&#8217;s look at what you&#8217;ve done here step by step:<\/p>\n<p><script src=\"https:\/\/gist.github.com\/asime\/10874314.js\"><\/script><\/p>\n<p>Simple enough, you&#8217;re setting an user array named\u00a0<strong>new_users<\/strong>, where each user is depicted as a hash containing a user name, the respective user&#8217;s full name, and finally their password.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/asime\/10874533.js\"><\/script><\/p>\n<p>Now, a common mistake when iterating over data sets in order to create thrift mutations is to place all mutations under the <strong>thrift_mutations<\/strong> array. This is a big no no, since once you run the mutateRow method, you are going to perform all mutations (all 3 in this case) while pointing them to a single row, and for your current purposes you would end up with a single row containing John Doe&#8217;s user as opposed to 3 rows containing Alice, Bob and John.<\/p>\n<p>To avoid this, on the previous code, you instantiate an\u00a0<strong>user_row_mutations<\/strong> array. And right after that you simply iterate over the <strong>new_users<\/strong> array appending the mutations for each user column onto the\u00a0<strong>thrift_mutations<\/strong> array. And finally you append the\u00a0<strong>thrift_mutations<\/strong> onto the\u00a0<strong>user_row_mutations<\/strong>. Quite a mouth-full, right? Yet not complex at all.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/asime\/10875214.js\"><\/script><\/p>\n<p>To top it all off, you will simply iterate over each of the\u00a0<strong>user_row_mutations<\/strong>&#8216; arrays and execute\u00a0<strong>mutateRow<\/strong> for each of them. This would be the SQL equivalent of sending three separate INSERT sentences over to the database.<\/p>\n<h2>Using HBase Filters to Get What You Need<\/h2>\n<p>If you&#8217;ve made it this far, you&#8217;re a champ. We&#8217;re nearing the end of this quick and painless tutorial so don&#8217;t go anywhere just yet.<\/p>\n<p>Ok, you now have a data set. Wonderful!<\/p>\n<p>\u00abHow do I retrieve my data now?\u00bb, you ask? Simple. Using the HBase Filter Language.<\/p>\n<p>HBase Filters allow you to ask HBase for data, given certain restrictions provided within the filters themselves. They are simple and you can group them together to perform complex data retrieval.<\/p>\n<p>As of writing this article, HBase&#8217;s Filter Language is very scarcely documented with many syntactic errors found on the\u00a0<strong><a href=\"https:\/\/hbase.apache.org\/book\/thrift.html\">documentation provided by Apache<\/a><\/strong>. Even so, the filters themselves are actually very functional and so far I haven&#8217;t encountered any bugs using them. So I&#8217;ll do my best to describe thoroughly the ones I&#8217;ll be showing you here, and soon I&#8217;ll do a post dedicated solely to HBase Filters.<\/p>\n<p>First thing you&#8217;ll need to learn about HBase Filters is their syntax. HBase Filters look a lot like functions to which you pass arguments to. Most filters will more or less follow this syntax:<\/p>\n<p><script src=\"https:\/\/gist.github.com\/asime\/10986209.js\"><\/script><\/p>\n<p>Part by part,\u00a0<strong>FilterName<\/strong> is obviously the name of the filter you are going to use (in this post I&#8217;ll be showing you the\u00a0<strong>ValueFilter<\/strong> and the <strong>DependentColumnFilter<\/strong>), the\u00a0<strong>condition<\/strong> will be a conditional operator such as (but not limited to)\u00a0<strong>=<\/strong> or\u00a0<strong>!=<\/strong>, and the\u00a0<strong>&#8216;comparator:value&#8217; <\/strong>pair should be looked at as two pieces of the puzzle where the\u00a0<strong>comparator<\/strong> will be determining how the\u00a0<strong>value<\/strong> will be affected by the\u00a0<strong>condition<\/strong>.<\/p>\n<p>There are several\u00a0<strong>comparators<\/strong>. In this post we&#8217;ll be looking at the\u00a0<strong>binary<\/strong> and the\u00a0<strong>regexstring <\/strong>comparators.<\/p>\n<p>Let&#8217;s put these concepts together with an example of the\u00a0<strong>ValueFilter<\/strong>.<\/p>\n<p>The\u00a0<strong>ValueFilter <\/strong>is basically the SQL equivalent of the WHERE and the LIKE clauses merged together.<\/p>\n<p>Taking into account the &#8216;<strong>user_table&#8217;<\/strong> data set, let&#8217;s say you want to retrieve the user named\u00a0<strong>&#8216;John Doe&#8217;<\/strong>. First you must lookup the value <strong>&#8216;John Doe&#8217;<\/strong>. Using the\u00a0<strong>ValueFilter<\/strong> it would look like the following:<\/p>\n<p><script src=\"https:\/\/gist.github.com\/asime\/10986257.js\"><\/script><\/p>\n<p>Quite easy, right? All you&#8217;re doing here is telling the <strong>ValueFilter<\/strong> to grab the columns&#8217; values and return a\u00a0<strong>binary<\/strong> result of either true or false as to whether the value matches <strong>&#8216;John Doe&#8217;<\/strong>. If The <strong>binary<\/strong> result is true, the matching row will be served.<\/p>\n<p>Now, you might be wondering \u00abhow come we&#8217;re not mentioning what column we want the <strong>ValueFilter<\/strong> to be run against anywhere on the previous code?\u00bb, and that my dear Watson, is a very astute inquiry!<\/p>\n<p>HBase Filters, by nature, can be grouped together by\u00a0<strong>AND<\/strong> and\u00a0<strong>OR<\/strong> operators very much like you can send various\u00a0<strong>WHERE<\/strong> and <strong>LIKE<\/strong> clauses grouped together in SQL by saying something like \u00abWHERE a=&#8217;b&#8217; AND x LIKE &#8216;%z&#8217; OR&#8230; etc\u00bb. So, in order to filter out only the rows which contain the value\u00a0<strong>&#8216;John Doe&#8217;<\/strong> inside the column named\u00a0<strong>&#8216;full_name&#8217;<\/strong> you will be grouping the <strong>ValueFilter<\/strong> you just wrote with the following <strong>DependentColumnFilter<\/strong>:<\/p>\n<p><script src=\"https:\/\/gist.github.com\/asime\/10986299.js\"><\/script><\/p>\n<p>All you&#8217;re saying here is \u00abgive me all rows which contain the column\u00a0<strong>&#8216;full_name&#8217;<\/strong>\u00ab. I should note that the second argument you see there, to which you are passing an empty string to, is meant for a column qualifier, but for now you&#8217;re solid just by providing the column name.<\/p>\n<p>Now let&#8217;s group your filters and see what we come up with:<\/p>\n<p><script src=\"https:\/\/gist.github.com\/asime\/10986339.js\"><\/script><\/p>\n<p>Before detailing what you&#8217;re doing here with the filters let me explain that the order in which you place your filters will indeed affect your end result so be very careful with that.<\/p>\n<p>By placing the filters the way you just did there, you are saying: \u00abFetch all rows that contain a value\u00a0<strong>&#8216;John Doe&#8217;<\/strong> in any of their columns, <strong>AND<\/strong> after that take those resulting rows and bring forth only those who have a column named <strong>&#8216;full_name&#8217;<\/strong> which contains the value\u00bb.<\/p>\n<p>With all of the above you should now have a pretty useful understanding of how the HBase Filter Language works. Let&#8217;s put this knowledge to practice.<\/p>\n<p>From the example files bring up the file named\u00a0<strong>hbase-get-row.rb<\/strong> and replace on the following line the filter string with the filter you just wrote:<\/p>\n<p><script src=\"https:\/\/gist.github.com\/asime\/10986416.js\"><\/script><\/p>\n<p>It should now look like this:<\/p>\n<p><script src=\"https:\/\/gist.github.com\/asime\/10875766.js\"><\/script><\/p>\n<p>The arguments you are passing to the <strong>get<\/strong> method are the table name (<strong>&#8216;user_table&#8217;<\/strong>), an array specifying what columns should be included in the final result array (<strong>&#8216;*&#8217;<\/strong> means all columns), the filters to run when retrieving the rows, and one optional hash meant for other advanced options you shall not worry about today.<\/p>\n<p>If you run this ruby script now you should be able to see John Doe&#8217;s information in all its glory.<\/p>\n<p>You have just created a request which would equate to saying in SQL \u00abSELECT * FROM user_table WHERE full_name=&#8217;John Doe'\u00bb. But this might not be useful for long once you need to do more complex comparisons using wildcards similar to when you would normally use LIKE in SQL. And that&#8217;s where the &lt;b&gt;regexstring&lt;\/b&gt; comparator comes into play.<\/p>\n<p>Let&#8217;s say that you wanted all users whose full name would include the string\u00a0<strong>&#8216;Will&#8217;<\/strong> somewhere in it. In SQL you would normally say \u00abSELECT * FROM user_table WHERE full_name LIKE &#8216;%Will%'\u00bb, but what you want to write to get the same result from HBase will be:<\/p>\n<p><script src=\"https:\/\/gist.github.com\/asime\/10876179.js\"><\/script><\/p>\n<p>Short and simple. All you did there was exchange the\u00a0<strong>&#8216;binary:John Doe&#8217;<\/strong> portion of your <strong>ValueFilter<\/strong> with <strong>&#8216;regexstring:.*Will.*&#8217;<\/strong> et voil\u00e1, for your intents and purposes you should&#8217;ve gotten back the user information for Bob Williams.<\/p>\n<p>This concludes this HBase tutorial. I wish I could refer you to more information about HBase mutations and filters, but as I mentioned before documentation on these subjects is still scarce. In the mean-time I&#8217;ll continue churning out more tutorials and articles on the subjects.<\/p>\n<p><strong>Until next time, Happy Hadoop&#8217;ing.<\/strong><\/p>","protected":false},"excerpt":{"rendered":"<p>AgilityFeat is notable for attracting clients who are looking into working with cutting-edge technologies, and thanks to that, recently, I&#8217;ve had the amazing opportunity to dive in head-first into researching about and experimenting with various BigData technologies, focusing mainly on Hadoop. Hadoop is still at a very early adoption stage, which has proven to be [&hellip;]<\/p>","protected":false},"author":11,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_et_pb_use_builder":"","_et_pb_old_content":"","_et_gb_content_width":""},"categories":[57],"tags":[125,126,127,103,104],"jetpack_featured_media_url":"","yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v15.7 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Managing data in HBase using Ruby and Thrift - AgilityFeat Panama Software Test Center<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/agilityfeatpanama.com\/en\/blog\/2014\/04\/managing-data-in-hbase-using-ruby-and-thrift\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Managing data in HBase using Ruby and Thrift - AgilityFeat Panama Software Test Center\" \/>\n<meta property=\"og:description\" content=\"AgilityFeat is notable for attracting clients who are looking into working with cutting-edge technologies, and thanks to that, recently, I&#8217;ve had the amazing opportunity to dive in head-first into researching about and experimenting with various BigData technologies, focusing mainly on Hadoop. Hadoop is still at a very early adoption stage, which has proven to be [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/agilityfeatpanama.com\/en\/blog\/2014\/04\/managing-data-in-hbase-using-ruby-and-thrift\/\" \/>\n<meta property=\"og:site_name\" content=\"AgilityFeat Panama Software Test Center\" \/>\n<meta property=\"article:published_time\" content=\"2014-04-14T22:11:18+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2020-11-10T20:46:32+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/agilityfeatpanama.com\/wp-content\/uploads\/2014\/04\/hbase_data_structure_chart.jpg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\">\n\t<meta name=\"twitter:data1\" content=\"12 minutes\">\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebSite\",\"@id\":\"https:\/\/34.200.113.64\/#website\",\"url\":\"https:\/\/34.200.113.64\/\",\"name\":\"AgilityFeat Panama Software Test Center\",\"description\":\"AgilityFeat Panama offers customized, multilevel web and mobile software testing for a variety of industries.\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":\"https:\/\/34.200.113.64\/?s={search_term_string}\",\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/agilityfeatpanama.com\/blog\/2014\/04\/managing-data-in-hbase-using-ruby-and-thrift\/#primaryimage\",\"inLanguage\":\"en-US\",\"url\":\"\/wp-content\/uploads\/2014\/04\/hbase_data_structure_chart.jpg\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/agilityfeatpanama.com\/blog\/2014\/04\/managing-data-in-hbase-using-ruby-and-thrift\/#webpage\",\"url\":\"https:\/\/agilityfeatpanama.com\/blog\/2014\/04\/managing-data-in-hbase-using-ruby-and-thrift\/\",\"name\":\"Managing data in HBase using Ruby and Thrift - AgilityFeat Panama Software Test Center\",\"isPartOf\":{\"@id\":\"https:\/\/34.200.113.64\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/agilityfeatpanama.com\/blog\/2014\/04\/managing-data-in-hbase-using-ruby-and-thrift\/#primaryimage\"},\"datePublished\":\"2014-04-14T22:11:18+00:00\",\"dateModified\":\"2020-11-10T20:46:32+00:00\",\"author\":{\"@id\":\"https:\/\/34.200.113.64\/#\/schema\/person\/c34873e9bee22a4aab19b8a0e800aad0\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/agilityfeatpanama.com\/blog\/2014\/04\/managing-data-in-hbase-using-ruby-and-thrift\/\"]}]},{\"@type\":\"Person\",\"@id\":\"https:\/\/34.200.113.64\/#\/schema\/person\/c34873e9bee22a4aab19b8a0e800aad0\",\"name\":\"Jean\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/34.200.113.64\/#personlogo\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/?s=96&d=mm&r=g\",\"caption\":\"Jean\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","_links":{"self":[{"href":"https:\/\/agilityfeatpanama.com\/en\/wp-json\/wp\/v2\/posts\/893"}],"collection":[{"href":"https:\/\/agilityfeatpanama.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/agilityfeatpanama.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/agilityfeatpanama.com\/en\/wp-json\/wp\/v2\/users\/11"}],"replies":[{"embeddable":true,"href":"https:\/\/agilityfeatpanama.com\/en\/wp-json\/wp\/v2\/comments?post=893"}],"version-history":[{"count":1,"href":"https:\/\/agilityfeatpanama.com\/en\/wp-json\/wp\/v2\/posts\/893\/revisions"}],"predecessor-version":[{"id":1361,"href":"https:\/\/agilityfeatpanama.com\/en\/wp-json\/wp\/v2\/posts\/893\/revisions\/1361"}],"wp:attachment":[{"href":"https:\/\/agilityfeatpanama.com\/en\/wp-json\/wp\/v2\/media?parent=893"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/agilityfeatpanama.com\/en\/wp-json\/wp\/v2\/categories?post=893"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/agilityfeatpanama.com\/en\/wp-json\/wp\/v2\/tags?post=893"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}