SlideShare a Scribd company logo
1 of 70
Download to read offline
Painless Data
Storage with
MongoDB & Go
• Author of Hugo, Cobra,
Viper & More
• Chief Developer
Advocate for MongoDB
• Gopher
@spf13
Why Go?
Why Another Language?
• Software is slow
• Sofware is hard to write
• Software doesn’t scale well
Go is Fast
• Go execution speed is close to C
• Go compile time rivals dynamic
interpretation
Go is Friendly
• Feels like a dynamic language in many ways
• Very small core language, easy to
remember all of it
• Single binary installation, no dependencies
• Extensive Tooling & StdLib
Go is Concurrent
• Concurrency is part of the language
• Any function can become a goroutine
• Goroutines run concurrently, communicate
through channels
• Select waits for communication on any of a
set of channels
MongoDB
Why Another Database?
• Databases are slow
• Relational structures don’t fit well
with modern programming
(ORMs)
• Databases don’t scale well
MongoDB is Fast
• Written in C++
• Extensive use of memory-mapped files 

i.e. read-through write-through memory caching.
• Runs nearly everywhere
• Data serialized as BSON (fast parsing)
• Full support for primary & secondary indexes
• Document model = less work
MongoDB is Friendly
• Ad Hoc queries
• Real time aggregation
• Rich query capabilities
• Traditionally consistent
• Geospatial features
• Support for most programming languages
• Flexible schema
MongoDB is “Web Scale”
• Built in sharding support distributes data
across many nodes
• MongoS intelligently routes to the correct
nodes
• Aggregation done in parallel across nodes
Document Database
• Not for .PDF & .DOC files
• A document is essentially an associative array
• Document == JSON object
• Document == PHP Array
• Document == Python Dict
• Document == Ruby Hash
• etc
Data Serialization
• Applications need persistant data
• The process of translating data structures
into a format that can be stored
• Ideal format accessible from many
languages
BSON
• Inspired by JSON
• Cross language binary serialization format
• Optimized for scanning
• Support for richer types
MongoDB &
Go
Go’s Data Types
• Go uses strict & static typing
• 2 Types are similar to a BSON document
• Struct
• Map
bob := &Person{
Name: "Bob",
Birthday: time.Now(),
}
!
data, err := bson.Marshal(bob)
if err != nil {
return err
}
fmt.Printf("Data: %qn", data)

!
var person Person
err = bson.Unmarshal(data, &person)
if err != nil {
return err
}
fmt.Printf("Person: %vn", person)
Serializing with BSON
bob := &Person{
Name: "Bob",
Birthday: time.Now(),
}
!
data, err := bson.Marshal(bob)
if err != nil {
return err
}
fmt.Printf("Data: %qn", data)

!
var person Person
err = bson.Unmarshal(data, &person)
if err != nil {
return err
}
fmt.Printf("Person: %vn", person)
Serializing with BSON
bob := &Person{
Name: "Bob",
Birthday: time.Now(),
}
!
data, err := bson.Marshal(bob)
if err != nil {
return err
}
fmt.Printf("Data: %qn", data)

!
var person Person
err = bson.Unmarshal(data, &person)
if err != nil {
return err
}
fmt.Printf("Person: %vn", person)
Serializing with BSON
bob := &Person{
Name: "Bob",
Birthday: time.Now(),
}
!
data, err := bson.Marshal(bob)
if err != nil {
return err
}
fmt.Printf("Data: %qn", data)

!
var person Person
err = bson.Unmarshal(data, &person)
if err != nil {
return err
}
fmt.Printf("Person: %vn", person)
Serializing with BSON
bob := &Person{
Name: "Bob",
Birthday: time.Now(),
}
!
data, err := bson.Marshal(bob)
if err != nil {
return err
}
fmt.Printf("Data: %qn", data)

!
var person Person
err = bson.Unmarshal(data, &person)
if err != nil {
return err
}
fmt.Printf("Person: %vn", person)
Serializing with BSON
bob := &Person{
Name: "Bob",
Birthday: time.Now(),
}
!
data, err := bson.Marshal(bob)
if err != nil {
return err
}
fmt.Printf("Data: %qn", data)

!
var person Person
err = bson.Unmarshal(data, &person)
if err != nil {
return err
}
fmt.Printf("Person: %vn", person)
Serializing with BSON
Data: "%x00x00x00x02name
x00x04x00x00x00Bob
x00tbirthdayx00x80rx97|
^x00x00x00x00"

!
Person: {Bob 2014-07-21
18:00:00 -0500 EST}
!
type Project struct {
Name string `bson:"name"`
ImportPath string `bson:"importPath"`
}
project := Project{name, path}
!
!
!
project := map[string]string{"name": name, "importPath": path}
!
!
!
project := bson.D{{"name", name}, {"importPath", path}}
Equal After Marshaling
Struct
Custom Map
Document Slice
mgo (mango)
• Pure Go
• Created in late 2010 

("Where do I put my Go data?")
• Adopted by Canonical and MongoDB Inc.
itself
• Sponsored by MongoDB Inc. from late 2011
Connecting
• Same interface for server, replica set, or shard
• Driver discovers and maintains topology
• Server added/removed, failovers, response times, etc
Connecting
session, err := mgo.Dial("localhost")
if err != nil {
return err
}
• Sessions are lightweight
• Sessions are copied (settings preserved)
• Single management goroutine for all copied sessions
Sessions
func (s *Server) handle(w http.ResponseWriter, r *http.Request) {
session := s.session.Copy()
defer session.Close()
// ... handle request ...
}
• Saves typing
• Uses the same session over and over
Convenient Access
projects := session.DB("OSCON").C("projects")
Writing
type Project struct {
Name string `bson:"name,omitempty"`
ImportPath string `bson:"importPath,omitempty"`
}
Defining Our Own Type
var projectList = []Project{
{"gocheck", "gopkg.in/check.v1"},
{"qml", "gopkg.in/qml.v0"},
{"pipe", "gopkg.in/pipe.v2"},
{"yaml", "gopkg.in/yaml.v1"},
}
!
for _, project := range projectList {
err := projects.Insert(project)
if err != nil {
return err
}
}
fmt.Println("Okay!")
Insert
Okay!
type M map[string]interface{}
!
change := M{"$set": Project{ImportPath: "gopkg.in/
qml.v1"}}
!
err = projects.Update(Project{Name: "qml"}, change)
if err != nil {
return err
}
!
fmt.Println("Done!")
Update
Done!
Querying
var project Project
!
err = projects.Find(Project{Name: "qml"}).One(&project)
if err != nil {
return err
}
!
fmt.Printf("Project: %vn", project)
Find
Project: 

{qml gopkg.in/qml.v0}
iter := projects.Find(nil).Iter()
!
var project Project
for iter.Next(&project) {
fmt.Printf("Project: %vn", project)
}
!
return iter.Err()
Iterate
Project: {gocheck gopkg.in/check.v1}
Project: {qml gopkg.in/qml.v0}
Project: {pipe gopkg.in/pipe.v2}
Project: {yaml gopkg.in/yaml.v1}
m := map[string]interface{}{
"name": "godep",
"tags": []string{"tool", "dependency"},
"contact": bson.M{
"name": "Keith Rarick",
"email": "kr@nospam.com",
},
}
!
err = projects.Insert(m)
if err != nil {
return err
}
fmt.Println("Okay!")
Nesting
Okay!
type Contact struct {
Name string
Email string
}
!
type Project struct {
Name string
Tags []string `bson:",omitempty"`
Contact Contact `bson:",omitempty"`
}
!
err = projects.Find(Project{Name: "godep"}).One(&project)
if err != nil {
return err
}
!
pretty.Println("Project:", project)
Nesting II
Project: main.Project{
Name: "godep",
Tags: {"tool",
"dependency"},
Contact: {Name:"Keith
Rarick", Email:"kr@XZY.com"},
}
• Compound
• List indexing (think tag lists)
• Geospatial
• Dense or sparse
• Full-text searching
Indexing
// Root field
err = projects.EnsureIndexKey("name")
...
!
// Nested field
err = projects.EnsureIndexKey("author.email")
...
Concurrency
func f(projects *mgo.Collection, name string, done chan error) {
var project Project
err := projects.Find(Project{Name: name}).One(&project)
if err == nil {
fmt.Printf("Project: %vn", project)
}
done <- err
}
!
done := make(chan error)
!
go f(projects, "qml", done)
go f(projects, "gocheck", done)
!
if err = firstError(2, done); err != nil {
return err
}
Concurrent
func f(projects *mgo.Collection, name string, done chan error) {
var project Project
err := projects.Find(Project{Name: name}).One(&project)
if err == nil {
fmt.Printf("Project: %vn", project)
}
done <- err
}
!
done := make(chan error)
!
go f(projects, "qml", done)
go f(projects, "gocheck", done)
!
if err = firstError(2, done); err != nil {
return err
}
Concurrent
func f(projects *mgo.Collection, name string, done chan error) {
var project Project
err := projects.Find(Project{Name: name}).One(&project)
if err == nil {
fmt.Printf("Project: %vn", project)
}
done <- err
}
!
done := make(chan error)
!
go f(projects, "qml", done)
go f(projects, "gocheck", done)
!
if err = firstError(2, done); err != nil {
return err
}
Concurrent
func f(projects *mgo.Collection, name string, done chan error) {
var project Project
err := projects.Find(Project{Name: name}).One(&project)
if err == nil {
fmt.Printf("Project: %vn", project)
}
done <- err
}
!
done := make(chan error)
!
go f(projects, "qml", done)
go f(projects, "gocheck", done)
!
if err = firstError(2, done); err != nil {
return err
}
Concurrent
func f(projects *mgo.Collection, name string, done chan error) {
var project Project
err := projects.Find(Project{Name: name}).One(&project)
if err == nil {
fmt.Printf("Project: %vn", project)
}
done <- err
}
!
done := make(chan error)
!
go f(projects, "qml", done)
go f(projects, "gocheck", done)
!
if err = firstError(2, done); err != nil {
return err
}
Concurrent
func f(projects *mgo.Collection, name string, done chan error) {
var project Project
err := projects.Find(Project{Name: name}).One(&project)
if err == nil {
fmt.Printf("Project: %vn", project)
}
done <- err
}
!
done := make(chan error)
!
go f(projects, "qml", done)
go f(projects, "gocheck", done)
!
if err = firstError(2, done); err != nil {
return err
}
Concurrent
func f(projects *mgo.Collection, name string, done chan error) {
var project Project
err := projects.Find(Project{Name: name}).One(&project)
if err == nil {
fmt.Printf("Project: %vn", project)
}
done <- err
}
!
done := make(chan error)
!
go f(projects, "qml", done)
go f(projects, "gocheck", done)
!
if err = firstError(2, done); err != nil {
return err
}
Concurrent
func f(projects *mgo.Collection, name string, done chan error) {
var project Project
err := projects.Find(Project{Name: name}).One(&project)
if err == nil {
fmt.Printf("Project: %vn", project)
}
done <- err
}
!
done := make(chan error)
!
go f(projects, "qml", done)
go f(projects, "gocheck", done)
!
if err = firstError(2, done); err != nil {
return err
}
Concurrent
Project: {qml gopkg.in/qml.v1}
Project: {gocheck gopkg.in/
check.v1}
• Find 1 issued

• Doc 1 returned
• Find 2 issued

• Doc 2 returned
A Common Approach
Find 1 Find 2 DB
}
}
• Find 1 issued
• Find 2 issued

• Doc 1 returned
• Doc 2 returned
Concurrent Queries
Find 1 Find 2 DB
}
}
• Loads 200 results at a time
• Loads next batch with (0.25 * 200) results left to process
Concurrent Loading
session.SetBatch(200)
session.SetPrefetch(0.25)
!
for iter.Next(&result) {
...
}
• Each Copy uses a different connection
• Closing session returns socket to the pool
• defer runs at end of function
Handler With Session Copy
func (s *Server) handle(w http.ResponseWriter, r *http.Request) {
session := s.session.Copy()
defer session.Close()
!
// ... handle request ...
}
• Shares a single connection
• Still quite efficient thanks to concurrent capabilities of go + mgo
Handler With Single Session
func (s *Server) handle(w http.ResponseWriter, r *http.Request) {
session := s.session
!
// ... handle request ...
}
GridFS
GridFS
• Not quite a file system
• Really useful for local file storage
• A convention, not a feature
• Supported by all drivers
• Fully replicated, sharded file storage
gridfs := session.DB("OSCON").GridFS("fs")
!
file, err := gridfs.Create("cd.iso")
if err != nil {
return err
}
defer file.Close()
!
started := time.Now()
!
_, err = io.Copy(file, iso)
if err != nil {
return err
}
!
fmt.Printf("Wrote %d bytes in %sn", file.Size(), time.Since(started))
GridFS
gridfs := session.DB("OSCON").GridFS("fs")
!
file, err := gridfs.Create("cd.iso")
if err != nil {
return err
}
defer file.Close()
!
started := time.Now()
!
_, err = io.Copy(file, iso)
if err != nil {
return err
}
!
fmt.Printf("Wrote %d bytes in %sn", file.Size(), time.Since(started))
GridFS
gridfs := session.DB("OSCON").GridFS("fs")
!
file, err := gridfs.Create("cd.iso")
if err != nil {
return err
}
defer file.Close()
!
started := time.Now()
!
_, err = io.Copy(file, iso)
if err != nil {
return err
}
!
fmt.Printf("Wrote %d bytes in %sn", file.Size(), time.Since(started))
GridFS
gridfs := session.DB("OSCON").GridFS("fs")
!
file, err := gridfs.Create("cd.iso")
if err != nil {
return err
}
defer file.Close()
!
started := time.Now()
!
_, err = io.Copy(file, iso)
if err != nil {
return err
}
!
fmt.Printf("Wrote %d bytes in %sn", file.Size(), time.Since(started))
GridFS
gridfs := session.DB("OSCON").GridFS("fs")
!
file, err := gridfs.Create("cd.iso")
if err != nil {
return err
}
defer file.Close()
!
started := time.Now()
!
_, err = io.Copy(file, iso)
if err != nil {
return err
}
!
fmt.Printf("Wrote %d bytes in %sn", file.Size(), time.Since(started))
GridFS
gridfs := session.DB("OSCON").GridFS("fs")
!
file, err := gridfs.Create("cd.iso")
if err != nil {
return err
}
defer file.Close()
!
started := time.Now()
!
_, err = io.Copy(file, iso)
if err != nil {
return err
}
!
fmt.Printf("Wrote %d bytes in %sn", file.Size(), time.Since(started))
GridFS
gridfs := session.DB("OSCON").GridFS("fs")
!
file, err := gridfs.Create("cd.iso")
if err != nil {
return err
}
defer file.Close()
!
started := time.Now()
!
_, err = io.Copy(file, iso)
if err != nil {
return err
}
!
fmt.Printf("Wrote %d bytes in %sn", file.Size(), time.Since(started))
GridFS
!
Wrote 470386961 bytes in 7.0s
Full Featured
Features
• Transactions (mgo/txn experiment)
• Aggregation pipelines
• Full-text search
• Geospatial support
• Hadoop
In Conclusion
Getting Started
• 1. Install MongoDB
• 2. go get gopkg.in/mgo.v2
• 3. Start small
• 4. Build something great
Learning More
• MongoDB Manual
• Effective Go
• labix.org/mgo
Workshop Using mgo on spf13.com
on spf13.com
• @spf13
• Author of Hugo, Cobra,
Viper & More
• Chief Developer
Advocate for MongoDB
• Gopher
Thank You

More Related Content

What's hot

Vim Script Programming
Vim Script ProgrammingVim Script Programming
Vim Script Programming
Lin Yo-An
 
JRuby: Pushing the Java Platform Further
JRuby: Pushing the Java Platform FurtherJRuby: Pushing the Java Platform Further
JRuby: Pushing the Java Platform Further
Charles Nutter
 

What's hot (20)

Happy Go Programming
Happy Go ProgrammingHappy Go Programming
Happy Go Programming
 
Introduction to Programming in Go
Introduction to Programming in GoIntroduction to Programming in Go
Introduction to Programming in Go
 
JRuby with Java Code in Data Processing World
JRuby with Java Code in Data Processing WorldJRuby with Java Code in Data Processing World
JRuby with Java Code in Data Processing World
 
Golang Performance : microbenchmarks, profilers, and a war story
Golang Performance : microbenchmarks, profilers, and a war storyGolang Performance : microbenchmarks, profilers, and a war story
Golang Performance : microbenchmarks, profilers, and a war story
 
Elegant concurrency
Elegant concurrencyElegant concurrency
Elegant concurrency
 
Vim Script Programming
Vim Script ProgrammingVim Script Programming
Vim Script Programming
 
Php’s guts
Php’s gutsPhp’s guts
Php’s guts
 
Fluentd v0.14 Plugin API Details
Fluentd v0.14 Plugin API DetailsFluentd v0.14 Plugin API Details
Fluentd v0.14 Plugin API Details
 
Fluentd meetup in japan
Fluentd meetup in japanFluentd meetup in japan
Fluentd meetup in japan
 
Using Logstash, elasticsearch & kibana
Using Logstash, elasticsearch & kibanaUsing Logstash, elasticsearch & kibana
Using Logstash, elasticsearch & kibana
 
Fluentd unified logging layer
Fluentd   unified logging layerFluentd   unified logging layer
Fluentd unified logging layer
 
JRuby: Pushing the Java Platform Further
JRuby: Pushing the Java Platform FurtherJRuby: Pushing the Java Platform Further
JRuby: Pushing the Java Platform Further
 
Fluentd at HKOScon
Fluentd at HKOSconFluentd at HKOScon
Fluentd at HKOScon
 
Learning Python from Data
Learning Python from DataLearning Python from Data
Learning Python from Data
 
Bringing Concurrency to Ruby - RubyConf India 2014
Bringing Concurrency to Ruby - RubyConf India 2014Bringing Concurrency to Ruby - RubyConf India 2014
Bringing Concurrency to Ruby - RubyConf India 2014
 
Dive into Fluentd plugin v0.12
Dive into Fluentd plugin v0.12Dive into Fluentd plugin v0.12
Dive into Fluentd plugin v0.12
 
Fluentd meetup #2
Fluentd meetup #2Fluentd meetup #2
Fluentd meetup #2
 
Modern Black Mages Fighting in the Real World
Modern Black Mages Fighting in the Real WorldModern Black Mages Fighting in the Real World
Modern Black Mages Fighting in the Real World
 
Logstash-Elasticsearch-Kibana
Logstash-Elasticsearch-KibanaLogstash-Elasticsearch-Kibana
Logstash-Elasticsearch-Kibana
 
Go Web Development
Go Web DevelopmentGo Web Development
Go Web Development
 

Viewers also liked

OSCON 2012 MongoDB Tutorial
OSCON 2012 MongoDB TutorialOSCON 2012 MongoDB Tutorial
OSCON 2012 MongoDB Tutorial
Steven Francia
 

Viewers also liked (8)

The Future of the Operating System - Keynote LinuxCon 2015
The Future of the Operating System -  Keynote LinuxCon 2015The Future of the Operating System -  Keynote LinuxCon 2015
The Future of the Operating System - Keynote LinuxCon 2015
 
What every successful open source project needs
What every successful open source project needsWhat every successful open source project needs
What every successful open source project needs
 
Big data for the rest of us
Big data for the rest of usBig data for the rest of us
Big data for the rest of us
 
MongoDB, Hadoop and humongous data - MongoSV 2012
MongoDB, Hadoop and humongous data - MongoSV 2012MongoDB, Hadoop and humongous data - MongoSV 2012
MongoDB, Hadoop and humongous data - MongoSV 2012
 
OSCON 2012 MongoDB Tutorial
OSCON 2012 MongoDB TutorialOSCON 2012 MongoDB Tutorial
OSCON 2012 MongoDB Tutorial
 
A Recovering Java Developer Learns to Go
A Recovering Java Developer Learns to GoA Recovering Java Developer Learns to Go
A Recovering Java Developer Learns to Go
 
From SIP to WebRTC and vice versa
From SIP to WebRTC and vice versaFrom SIP to WebRTC and vice versa
From SIP to WebRTC and vice versa
 
(ARC346) Scaling To 25 Billion Daily Requests Within 3 Months On AWS
(ARC346) Scaling To 25 Billion Daily Requests Within 3 Months On AWS(ARC346) Scaling To 25 Billion Daily Requests Within 3 Months On AWS
(ARC346) Scaling To 25 Billion Daily Requests Within 3 Months On AWS
 

Similar to Painless Data Storage with MongoDB & Go

An Introduction to Go
An Introduction to GoAn Introduction to Go
An Introduction to Go
Cloudflare
 
Barcelona MUG MongoDB + Hadoop Presentation
Barcelona MUG MongoDB + Hadoop PresentationBarcelona MUG MongoDB + Hadoop Presentation
Barcelona MUG MongoDB + Hadoop Presentation
Norberto Leite
 
Node js
Node jsNode js
Node js
hazzaz
 
Asynchronous I/O in NodeJS - new standard or challenges?
Asynchronous I/O in NodeJS - new standard or challenges?Asynchronous I/O in NodeJS - new standard or challenges?
Asynchronous I/O in NodeJS - new standard or challenges?
Dinh Pham
 
MongoDB and Ruby on Rails
MongoDB and Ruby on RailsMongoDB and Ruby on Rails
MongoDB and Ruby on Rails
rfischer20
 

Similar to Painless Data Storage with MongoDB & Go (20)

An Introduction to Go
An Introduction to GoAn Introduction to Go
An Introduction to Go
 
Go from a PHP Perspective
Go from a PHP PerspectiveGo from a PHP Perspective
Go from a PHP Perspective
 
MongoDB at ZPUGDC
MongoDB at ZPUGDCMongoDB at ZPUGDC
MongoDB at ZPUGDC
 
#Pharo Days 2016 Data Formats and Protocols
#Pharo Days 2016 Data Formats and Protocols#Pharo Days 2016 Data Formats and Protocols
#Pharo Days 2016 Data Formats and Protocols
 
Allura - an Open Source MongoDB Based Document Oriented SourceForge
Allura - an Open Source MongoDB Based Document Oriented SourceForgeAllura - an Open Source MongoDB Based Document Oriented SourceForge
Allura - an Open Source MongoDB Based Document Oriented SourceForge
 
Barcelona MUG MongoDB + Hadoop Presentation
Barcelona MUG MongoDB + Hadoop PresentationBarcelona MUG MongoDB + Hadoop Presentation
Barcelona MUG MongoDB + Hadoop Presentation
 
Go for Rubyists
Go for RubyistsGo for Rubyists
Go for Rubyists
 
Rapid, Scalable Web Development with MongoDB, Ming, and Python
Rapid, Scalable Web Development with MongoDB, Ming, and PythonRapid, Scalable Web Development with MongoDB, Ming, and Python
Rapid, Scalable Web Development with MongoDB, Ming, and Python
 
Node js
Node jsNode js
Node js
 
Monitoring and Debugging your Live Applications
Monitoring and Debugging your Live ApplicationsMonitoring and Debugging your Live Applications
Monitoring and Debugging your Live Applications
 
AMD - Why, What and How
AMD - Why, What and HowAMD - Why, What and How
AMD - Why, What and How
 
Conexión de MongoDB con Hadoop - Luis Alberto Giménez - CAPSiDE #DevOSSAzureDays
Conexión de MongoDB con Hadoop - Luis Alberto Giménez - CAPSiDE #DevOSSAzureDaysConexión de MongoDB con Hadoop - Luis Alberto Giménez - CAPSiDE #DevOSSAzureDays
Conexión de MongoDB con Hadoop - Luis Alberto Giménez - CAPSiDE #DevOSSAzureDays
 
Unleash your inner console cowboy
Unleash your inner console cowboyUnleash your inner console cowboy
Unleash your inner console cowboy
 
Inroduction to golang
Inroduction to golangInroduction to golang
Inroduction to golang
 
Code for Startup MVP (Ruby on Rails) Session 2
Code for Startup MVP (Ruby on Rails) Session 2Code for Startup MVP (Ruby on Rails) Session 2
Code for Startup MVP (Ruby on Rails) Session 2
 
MongoDB Munich 2012: MongoDB for official documents in Bavaria
MongoDB Munich 2012: MongoDB for official documents in BavariaMongoDB Munich 2012: MongoDB for official documents in Bavaria
MongoDB Munich 2012: MongoDB for official documents in Bavaria
 
Practical Use of MongoDB for Node.js
Practical Use of MongoDB for Node.jsPractical Use of MongoDB for Node.js
Practical Use of MongoDB for Node.js
 
BreizhCamp 2013 - Pimp my backend
BreizhCamp 2013 - Pimp my backendBreizhCamp 2013 - Pimp my backend
BreizhCamp 2013 - Pimp my backend
 
Asynchronous I/O in NodeJS - new standard or challenges?
Asynchronous I/O in NodeJS - new standard or challenges?Asynchronous I/O in NodeJS - new standard or challenges?
Asynchronous I/O in NodeJS - new standard or challenges?
 
MongoDB and Ruby on Rails
MongoDB and Ruby on RailsMongoDB and Ruby on Rails
MongoDB and Ruby on Rails
 

More from Steven Francia

MongoDB, Hadoop and Humongous Data
MongoDB, Hadoop and Humongous DataMongoDB, Hadoop and Humongous Data
MongoDB, Hadoop and Humongous Data
Steven Francia
 
Hybrid MongoDB and RDBMS Applications
Hybrid MongoDB and RDBMS ApplicationsHybrid MongoDB and RDBMS Applications
Hybrid MongoDB and RDBMS Applications
Steven Francia
 
MongoDB and PHP ZendCon 2011
MongoDB and PHP ZendCon 2011MongoDB and PHP ZendCon 2011
MongoDB and PHP ZendCon 2011
Steven Francia
 
Blending MongoDB and RDBMS for ecommerce
Blending MongoDB and RDBMS for ecommerceBlending MongoDB and RDBMS for ecommerce
Blending MongoDB and RDBMS for ecommerce
Steven Francia
 

More from Steven Francia (19)

State of the Gopher Nation - Golang - August 2017
State of the Gopher Nation - Golang - August 2017State of the Gopher Nation - Golang - August 2017
State of the Gopher Nation - Golang - August 2017
 
Modern Database Systems (for Genealogy)
Modern Database Systems (for Genealogy)Modern Database Systems (for Genealogy)
Modern Database Systems (for Genealogy)
 
Introduction to MongoDB and Hadoop
Introduction to MongoDB and HadoopIntroduction to MongoDB and Hadoop
Introduction to MongoDB and Hadoop
 
Future of data
Future of dataFuture of data
Future of data
 
Replication, Durability, and Disaster Recovery
Replication, Durability, and Disaster RecoveryReplication, Durability, and Disaster Recovery
Replication, Durability, and Disaster Recovery
 
Multi Data Center Strategies
Multi Data Center StrategiesMulti Data Center Strategies
Multi Data Center Strategies
 
NoSQL databases and managing big data
NoSQL databases and managing big dataNoSQL databases and managing big data
NoSQL databases and managing big data
 
MongoDB, Hadoop and Humongous Data
MongoDB, Hadoop and Humongous DataMongoDB, Hadoop and Humongous Data
MongoDB, Hadoop and Humongous Data
 
MongoDB and hadoop
MongoDB and hadoopMongoDB and hadoop
MongoDB and hadoop
 
MongoDB for Genealogy
MongoDB for GenealogyMongoDB for Genealogy
MongoDB for Genealogy
 
Hybrid MongoDB and RDBMS Applications
Hybrid MongoDB and RDBMS ApplicationsHybrid MongoDB and RDBMS Applications
Hybrid MongoDB and RDBMS Applications
 
Building your first application w/mongoDB MongoSV2011
Building your first application w/mongoDB MongoSV2011Building your first application w/mongoDB MongoSV2011
Building your first application w/mongoDB MongoSV2011
 
MongoDB, E-commerce and Transactions
MongoDB, E-commerce and TransactionsMongoDB, E-commerce and Transactions
MongoDB, E-commerce and Transactions
 
MongoDB, PHP and the cloud - php cloud summit 2011
MongoDB, PHP and the cloud - php cloud summit 2011MongoDB, PHP and the cloud - php cloud summit 2011
MongoDB, PHP and the cloud - php cloud summit 2011
 
MongoDB and PHP ZendCon 2011
MongoDB and PHP ZendCon 2011MongoDB and PHP ZendCon 2011
MongoDB and PHP ZendCon 2011
 
MongoDB
MongoDBMongoDB
MongoDB
 
Blending MongoDB and RDBMS for ecommerce
Blending MongoDB and RDBMS for ecommerceBlending MongoDB and RDBMS for ecommerce
Blending MongoDB and RDBMS for ecommerce
 
Augmenting RDBMS with MongoDB for ecommerce
Augmenting RDBMS with MongoDB for ecommerceAugmenting RDBMS with MongoDB for ecommerce
Augmenting RDBMS with MongoDB for ecommerce
 
MongoDB and Ecommerce : A perfect combination
MongoDB and Ecommerce : A perfect combinationMongoDB and Ecommerce : A perfect combination
MongoDB and Ecommerce : A perfect combination
 

Recently uploaded

CORS (Kitworks Team Study 양다윗 발표자료 240510)
CORS (Kitworks Team Study 양다윗 발표자료 240510)CORS (Kitworks Team Study 양다윗 발표자료 240510)
CORS (Kitworks Team Study 양다윗 발표자료 240510)
Wonjun Hwang
 
“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf
“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf
“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf
Muhammad Subhan
 
Hyatt driving innovation and exceptional customer experiences with FIDO passw...
Hyatt driving innovation and exceptional customer experiences with FIDO passw...Hyatt driving innovation and exceptional customer experiences with FIDO passw...
Hyatt driving innovation and exceptional customer experiences with FIDO passw...
FIDO Alliance
 

Recently uploaded (20)

Vector Search @ sw2con for slideshare.pptx
Vector Search @ sw2con for slideshare.pptxVector Search @ sw2con for slideshare.pptx
Vector Search @ sw2con for slideshare.pptx
 
Generative AI Use Cases and Applications.pdf
Generative AI Use Cases and Applications.pdfGenerative AI Use Cases and Applications.pdf
Generative AI Use Cases and Applications.pdf
 
CORS (Kitworks Team Study 양다윗 발표자료 240510)
CORS (Kitworks Team Study 양다윗 발표자료 240510)CORS (Kitworks Team Study 양다윗 발표자료 240510)
CORS (Kitworks Team Study 양다윗 발표자료 240510)
 
Portal Kombat : extension du réseau de propagande russe
Portal Kombat : extension du réseau de propagande russePortal Kombat : extension du réseau de propagande russe
Portal Kombat : extension du réseau de propagande russe
 
Event-Driven Architecture Masterclass: Integrating Distributed Data Stores Ac...
Event-Driven Architecture Masterclass: Integrating Distributed Data Stores Ac...Event-Driven Architecture Masterclass: Integrating Distributed Data Stores Ac...
Event-Driven Architecture Masterclass: Integrating Distributed Data Stores Ac...
 
The Zero-ETL Approach: Enhancing Data Agility and Insight
The Zero-ETL Approach: Enhancing Data Agility and InsightThe Zero-ETL Approach: Enhancing Data Agility and Insight
The Zero-ETL Approach: Enhancing Data Agility and Insight
 
JohnPollard-hybrid-app-RailsConf2024.pptx
JohnPollard-hybrid-app-RailsConf2024.pptxJohnPollard-hybrid-app-RailsConf2024.pptx
JohnPollard-hybrid-app-RailsConf2024.pptx
 
“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf
“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf
“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf
 
Continuing Bonds Through AI: A Hermeneutic Reflection on Thanabots
Continuing Bonds Through AI: A Hermeneutic Reflection on ThanabotsContinuing Bonds Through AI: A Hermeneutic Reflection on Thanabots
Continuing Bonds Through AI: A Hermeneutic Reflection on Thanabots
 
Intro to Passkeys and the State of Passwordless.pptx
Intro to Passkeys and the State of Passwordless.pptxIntro to Passkeys and the State of Passwordless.pptx
Intro to Passkeys and the State of Passwordless.pptx
 
AI mind or machine power point presentation
AI mind or machine power point presentationAI mind or machine power point presentation
AI mind or machine power point presentation
 
AI+A11Y 11MAY2024 HYDERBAD GAAD 2024 - HelloA11Y (11 May 2024)
AI+A11Y 11MAY2024 HYDERBAD GAAD 2024 - HelloA11Y (11 May 2024)AI+A11Y 11MAY2024 HYDERBAD GAAD 2024 - HelloA11Y (11 May 2024)
AI+A11Y 11MAY2024 HYDERBAD GAAD 2024 - HelloA11Y (11 May 2024)
 
Hyatt driving innovation and exceptional customer experiences with FIDO passw...
Hyatt driving innovation and exceptional customer experiences with FIDO passw...Hyatt driving innovation and exceptional customer experiences with FIDO passw...
Hyatt driving innovation and exceptional customer experiences with FIDO passw...
 
State of the Smart Building Startup Landscape 2024!
State of the Smart Building Startup Landscape 2024!State of the Smart Building Startup Landscape 2024!
State of the Smart Building Startup Landscape 2024!
 
Top 10 CodeIgniter Development Companies
Top 10 CodeIgniter Development CompaniesTop 10 CodeIgniter Development Companies
Top 10 CodeIgniter Development Companies
 
Introduction to use of FHIR Documents in ABDM
Introduction to use of FHIR Documents in ABDMIntroduction to use of FHIR Documents in ABDM
Introduction to use of FHIR Documents in ABDM
 
(Explainable) Data-Centric AI: what are you explaininhg, and to whom?
(Explainable) Data-Centric AI: what are you explaininhg, and to whom?(Explainable) Data-Centric AI: what are you explaininhg, and to whom?
(Explainable) Data-Centric AI: what are you explaininhg, and to whom?
 
Introduction to FIDO Authentication and Passkeys.pptx
Introduction to FIDO Authentication and Passkeys.pptxIntroduction to FIDO Authentication and Passkeys.pptx
Introduction to FIDO Authentication and Passkeys.pptx
 
Simplifying Mobile A11y Presentation.pptx
Simplifying Mobile A11y Presentation.pptxSimplifying Mobile A11y Presentation.pptx
Simplifying Mobile A11y Presentation.pptx
 
Observability Concepts EVERY Developer Should Know (DevOpsDays Seattle)
Observability Concepts EVERY Developer Should Know (DevOpsDays Seattle)Observability Concepts EVERY Developer Should Know (DevOpsDays Seattle)
Observability Concepts EVERY Developer Should Know (DevOpsDays Seattle)
 

Painless Data Storage with MongoDB & Go

  • 2. • Author of Hugo, Cobra, Viper & More • Chief Developer Advocate for MongoDB • Gopher @spf13
  • 4. Why Another Language? • Software is slow • Sofware is hard to write • Software doesn’t scale well
  • 5. Go is Fast • Go execution speed is close to C • Go compile time rivals dynamic interpretation
  • 6. Go is Friendly • Feels like a dynamic language in many ways • Very small core language, easy to remember all of it • Single binary installation, no dependencies • Extensive Tooling & StdLib
  • 7. Go is Concurrent • Concurrency is part of the language • Any function can become a goroutine • Goroutines run concurrently, communicate through channels • Select waits for communication on any of a set of channels
  • 9. Why Another Database? • Databases are slow • Relational structures don’t fit well with modern programming (ORMs) • Databases don’t scale well
  • 10. MongoDB is Fast • Written in C++ • Extensive use of memory-mapped files 
 i.e. read-through write-through memory caching. • Runs nearly everywhere • Data serialized as BSON (fast parsing) • Full support for primary & secondary indexes • Document model = less work
  • 11. MongoDB is Friendly • Ad Hoc queries • Real time aggregation • Rich query capabilities • Traditionally consistent • Geospatial features • Support for most programming languages • Flexible schema
  • 12. MongoDB is “Web Scale” • Built in sharding support distributes data across many nodes • MongoS intelligently routes to the correct nodes • Aggregation done in parallel across nodes
  • 13. Document Database • Not for .PDF & .DOC files • A document is essentially an associative array • Document == JSON object • Document == PHP Array • Document == Python Dict • Document == Ruby Hash • etc
  • 14. Data Serialization • Applications need persistant data • The process of translating data structures into a format that can be stored • Ideal format accessible from many languages
  • 15. BSON • Inspired by JSON • Cross language binary serialization format • Optimized for scanning • Support for richer types
  • 17. Go’s Data Types • Go uses strict & static typing • 2 Types are similar to a BSON document • Struct • Map
  • 18. bob := &Person{ Name: "Bob", Birthday: time.Now(), } ! data, err := bson.Marshal(bob) if err != nil { return err } fmt.Printf("Data: %qn", data)
 ! var person Person err = bson.Unmarshal(data, &person) if err != nil { return err } fmt.Printf("Person: %vn", person) Serializing with BSON
  • 19. bob := &Person{ Name: "Bob", Birthday: time.Now(), } ! data, err := bson.Marshal(bob) if err != nil { return err } fmt.Printf("Data: %qn", data)
 ! var person Person err = bson.Unmarshal(data, &person) if err != nil { return err } fmt.Printf("Person: %vn", person) Serializing with BSON
  • 20. bob := &Person{ Name: "Bob", Birthday: time.Now(), } ! data, err := bson.Marshal(bob) if err != nil { return err } fmt.Printf("Data: %qn", data)
 ! var person Person err = bson.Unmarshal(data, &person) if err != nil { return err } fmt.Printf("Person: %vn", person) Serializing with BSON
  • 21. bob := &Person{ Name: "Bob", Birthday: time.Now(), } ! data, err := bson.Marshal(bob) if err != nil { return err } fmt.Printf("Data: %qn", data)
 ! var person Person err = bson.Unmarshal(data, &person) if err != nil { return err } fmt.Printf("Person: %vn", person) Serializing with BSON
  • 22. bob := &Person{ Name: "Bob", Birthday: time.Now(), } ! data, err := bson.Marshal(bob) if err != nil { return err } fmt.Printf("Data: %qn", data)
 ! var person Person err = bson.Unmarshal(data, &person) if err != nil { return err } fmt.Printf("Person: %vn", person) Serializing with BSON
  • 23. bob := &Person{ Name: "Bob", Birthday: time.Now(), } ! data, err := bson.Marshal(bob) if err != nil { return err } fmt.Printf("Data: %qn", data)
 ! var person Person err = bson.Unmarshal(data, &person) if err != nil { return err } fmt.Printf("Person: %vn", person) Serializing with BSON Data: "%x00x00x00x02name x00x04x00x00x00Bob x00tbirthdayx00x80rx97| ^x00x00x00x00"
 ! Person: {Bob 2014-07-21 18:00:00 -0500 EST}
  • 24. ! type Project struct { Name string `bson:"name"` ImportPath string `bson:"importPath"` } project := Project{name, path} ! ! ! project := map[string]string{"name": name, "importPath": path} ! ! ! project := bson.D{{"name", name}, {"importPath", path}} Equal After Marshaling Struct Custom Map Document Slice
  • 25. mgo (mango) • Pure Go • Created in late 2010 
 ("Where do I put my Go data?") • Adopted by Canonical and MongoDB Inc. itself • Sponsored by MongoDB Inc. from late 2011
  • 27. • Same interface for server, replica set, or shard • Driver discovers and maintains topology • Server added/removed, failovers, response times, etc Connecting session, err := mgo.Dial("localhost") if err != nil { return err }
  • 28. • Sessions are lightweight • Sessions are copied (settings preserved) • Single management goroutine for all copied sessions Sessions func (s *Server) handle(w http.ResponseWriter, r *http.Request) { session := s.session.Copy() defer session.Close() // ... handle request ... }
  • 29. • Saves typing • Uses the same session over and over Convenient Access projects := session.DB("OSCON").C("projects")
  • 31. type Project struct { Name string `bson:"name,omitempty"` ImportPath string `bson:"importPath,omitempty"` } Defining Our Own Type
  • 32. var projectList = []Project{ {"gocheck", "gopkg.in/check.v1"}, {"qml", "gopkg.in/qml.v0"}, {"pipe", "gopkg.in/pipe.v2"}, {"yaml", "gopkg.in/yaml.v1"}, } ! for _, project := range projectList { err := projects.Insert(project) if err != nil { return err } } fmt.Println("Okay!") Insert Okay!
  • 33. type M map[string]interface{} ! change := M{"$set": Project{ImportPath: "gopkg.in/ qml.v1"}} ! err = projects.Update(Project{Name: "qml"}, change) if err != nil { return err } ! fmt.Println("Done!") Update Done!
  • 35. var project Project ! err = projects.Find(Project{Name: "qml"}).One(&project) if err != nil { return err } ! fmt.Printf("Project: %vn", project) Find Project: 
 {qml gopkg.in/qml.v0}
  • 36. iter := projects.Find(nil).Iter() ! var project Project for iter.Next(&project) { fmt.Printf("Project: %vn", project) } ! return iter.Err() Iterate Project: {gocheck gopkg.in/check.v1} Project: {qml gopkg.in/qml.v0} Project: {pipe gopkg.in/pipe.v2} Project: {yaml gopkg.in/yaml.v1}
  • 37. m := map[string]interface{}{ "name": "godep", "tags": []string{"tool", "dependency"}, "contact": bson.M{ "name": "Keith Rarick", "email": "kr@nospam.com", }, } ! err = projects.Insert(m) if err != nil { return err } fmt.Println("Okay!") Nesting Okay!
  • 38. type Contact struct { Name string Email string } ! type Project struct { Name string Tags []string `bson:",omitempty"` Contact Contact `bson:",omitempty"` } ! err = projects.Find(Project{Name: "godep"}).One(&project) if err != nil { return err } ! pretty.Println("Project:", project) Nesting II Project: main.Project{ Name: "godep", Tags: {"tool", "dependency"}, Contact: {Name:"Keith Rarick", Email:"kr@XZY.com"}, }
  • 39. • Compound • List indexing (think tag lists) • Geospatial • Dense or sparse • Full-text searching Indexing // Root field err = projects.EnsureIndexKey("name") ... ! // Nested field err = projects.EnsureIndexKey("author.email") ...
  • 41. func f(projects *mgo.Collection, name string, done chan error) { var project Project err := projects.Find(Project{Name: name}).One(&project) if err == nil { fmt.Printf("Project: %vn", project) } done <- err } ! done := make(chan error) ! go f(projects, "qml", done) go f(projects, "gocheck", done) ! if err = firstError(2, done); err != nil { return err } Concurrent
  • 42. func f(projects *mgo.Collection, name string, done chan error) { var project Project err := projects.Find(Project{Name: name}).One(&project) if err == nil { fmt.Printf("Project: %vn", project) } done <- err } ! done := make(chan error) ! go f(projects, "qml", done) go f(projects, "gocheck", done) ! if err = firstError(2, done); err != nil { return err } Concurrent
  • 43. func f(projects *mgo.Collection, name string, done chan error) { var project Project err := projects.Find(Project{Name: name}).One(&project) if err == nil { fmt.Printf("Project: %vn", project) } done <- err } ! done := make(chan error) ! go f(projects, "qml", done) go f(projects, "gocheck", done) ! if err = firstError(2, done); err != nil { return err } Concurrent
  • 44. func f(projects *mgo.Collection, name string, done chan error) { var project Project err := projects.Find(Project{Name: name}).One(&project) if err == nil { fmt.Printf("Project: %vn", project) } done <- err } ! done := make(chan error) ! go f(projects, "qml", done) go f(projects, "gocheck", done) ! if err = firstError(2, done); err != nil { return err } Concurrent
  • 45. func f(projects *mgo.Collection, name string, done chan error) { var project Project err := projects.Find(Project{Name: name}).One(&project) if err == nil { fmt.Printf("Project: %vn", project) } done <- err } ! done := make(chan error) ! go f(projects, "qml", done) go f(projects, "gocheck", done) ! if err = firstError(2, done); err != nil { return err } Concurrent
  • 46. func f(projects *mgo.Collection, name string, done chan error) { var project Project err := projects.Find(Project{Name: name}).One(&project) if err == nil { fmt.Printf("Project: %vn", project) } done <- err } ! done := make(chan error) ! go f(projects, "qml", done) go f(projects, "gocheck", done) ! if err = firstError(2, done); err != nil { return err } Concurrent
  • 47. func f(projects *mgo.Collection, name string, done chan error) { var project Project err := projects.Find(Project{Name: name}).One(&project) if err == nil { fmt.Printf("Project: %vn", project) } done <- err } ! done := make(chan error) ! go f(projects, "qml", done) go f(projects, "gocheck", done) ! if err = firstError(2, done); err != nil { return err } Concurrent
  • 48. func f(projects *mgo.Collection, name string, done chan error) { var project Project err := projects.Find(Project{Name: name}).One(&project) if err == nil { fmt.Printf("Project: %vn", project) } done <- err } ! done := make(chan error) ! go f(projects, "qml", done) go f(projects, "gocheck", done) ! if err = firstError(2, done); err != nil { return err } Concurrent Project: {qml gopkg.in/qml.v1} Project: {gocheck gopkg.in/ check.v1}
  • 49. • Find 1 issued
 • Doc 1 returned • Find 2 issued
 • Doc 2 returned A Common Approach Find 1 Find 2 DB } }
  • 50. • Find 1 issued • Find 2 issued
 • Doc 1 returned • Doc 2 returned Concurrent Queries Find 1 Find 2 DB } }
  • 51. • Loads 200 results at a time • Loads next batch with (0.25 * 200) results left to process Concurrent Loading session.SetBatch(200) session.SetPrefetch(0.25) ! for iter.Next(&result) { ... }
  • 52. • Each Copy uses a different connection • Closing session returns socket to the pool • defer runs at end of function Handler With Session Copy func (s *Server) handle(w http.ResponseWriter, r *http.Request) { session := s.session.Copy() defer session.Close() ! // ... handle request ... }
  • 53. • Shares a single connection • Still quite efficient thanks to concurrent capabilities of go + mgo Handler With Single Session func (s *Server) handle(w http.ResponseWriter, r *http.Request) { session := s.session ! // ... handle request ... }
  • 55. GridFS • Not quite a file system • Really useful for local file storage • A convention, not a feature • Supported by all drivers • Fully replicated, sharded file storage
  • 56. gridfs := session.DB("OSCON").GridFS("fs") ! file, err := gridfs.Create("cd.iso") if err != nil { return err } defer file.Close() ! started := time.Now() ! _, err = io.Copy(file, iso) if err != nil { return err } ! fmt.Printf("Wrote %d bytes in %sn", file.Size(), time.Since(started)) GridFS
  • 57. gridfs := session.DB("OSCON").GridFS("fs") ! file, err := gridfs.Create("cd.iso") if err != nil { return err } defer file.Close() ! started := time.Now() ! _, err = io.Copy(file, iso) if err != nil { return err } ! fmt.Printf("Wrote %d bytes in %sn", file.Size(), time.Since(started)) GridFS
  • 58. gridfs := session.DB("OSCON").GridFS("fs") ! file, err := gridfs.Create("cd.iso") if err != nil { return err } defer file.Close() ! started := time.Now() ! _, err = io.Copy(file, iso) if err != nil { return err } ! fmt.Printf("Wrote %d bytes in %sn", file.Size(), time.Since(started)) GridFS
  • 59. gridfs := session.DB("OSCON").GridFS("fs") ! file, err := gridfs.Create("cd.iso") if err != nil { return err } defer file.Close() ! started := time.Now() ! _, err = io.Copy(file, iso) if err != nil { return err } ! fmt.Printf("Wrote %d bytes in %sn", file.Size(), time.Since(started)) GridFS
  • 60. gridfs := session.DB("OSCON").GridFS("fs") ! file, err := gridfs.Create("cd.iso") if err != nil { return err } defer file.Close() ! started := time.Now() ! _, err = io.Copy(file, iso) if err != nil { return err } ! fmt.Printf("Wrote %d bytes in %sn", file.Size(), time.Since(started)) GridFS
  • 61. gridfs := session.DB("OSCON").GridFS("fs") ! file, err := gridfs.Create("cd.iso") if err != nil { return err } defer file.Close() ! started := time.Now() ! _, err = io.Copy(file, iso) if err != nil { return err } ! fmt.Printf("Wrote %d bytes in %sn", file.Size(), time.Since(started)) GridFS
  • 62. gridfs := session.DB("OSCON").GridFS("fs") ! file, err := gridfs.Create("cd.iso") if err != nil { return err } defer file.Close() ! started := time.Now() ! _, err = io.Copy(file, iso) if err != nil { return err } ! fmt.Printf("Wrote %d bytes in %sn", file.Size(), time.Since(started)) GridFS ! Wrote 470386961 bytes in 7.0s
  • 64. Features • Transactions (mgo/txn experiment) • Aggregation pipelines • Full-text search • Geospatial support • Hadoop
  • 66. Getting Started • 1. Install MongoDB • 2. go get gopkg.in/mgo.v2 • 3. Start small • 4. Build something great
  • 67. Learning More • MongoDB Manual • Effective Go • labix.org/mgo
  • 68. Workshop Using mgo on spf13.com
  • 70. • @spf13 • Author of Hugo, Cobra, Viper & More • Chief Developer Advocate for MongoDB • Gopher Thank You