Pathis Patel
Pathis Patel

Reputation: 179

What is the exact size of a Firestore document?

The docs says that the size of a document is composed of:

  1. document name size
  2. The sum of the string size of each field name
  3. The sum of the size of each field value
  4. 32 additional bytes

The following document example:

Has the size of 147.

Question:

When calculating the size of a document, is there anything else I should care of? Perhaps some metadata? Because when using this calculation, there's for sure something missing.

I have this class:

class Points {
    public List<GeoPoint> geoPoints;

    public Points() {}

    public Points(List<GeoPoint> geoPoints) {
        this.geoPoints = geoPoints;
    }
}

And this is how I create the list and how I write it to the database:

List<GeoPoint> geoPoints = new ArrayList<>();
for (int i = 1; i <= 40_327 ; i++) {
    geoPoints.add(new GeoPoint(11.22, 33.44));
}
DocumentReference geoRef = db.collection("points\geo");
geoRef.set(new Points(geoPoints)).addOnCompleteListener(new OnCompleteListener<Void>() {
    @Override
    public void onComplete(@NonNull Task<Void> task) {
        if (task.isSuccessful()) {
            Log.d("TAG", "geoPoints added successfully");
        } else {
            Log.d("TAG", task.getException().getMessage());
        }
    }
});

Edit with example:

My reference is:

db.collection("points").document("geo");
  1. (6 + 1) + (3 + 1) + 16 = 27

The field name (the array) is called geoPoints

  1. 9 + 1 = 10

I store in that array 40,327

  1. 40,327 * 16 = 645,232

There is an additional 32 additional bytes for each document

  1. 32

So it makes a total of:

Total: 27 + 10 + 645,232 + 32 = 645,301 bytes

There is nowhere specified in the docs that each element in the array counts more than his length:

Field value size

The following table shows the size of field values by type.

Type Size

Array The sum of the sizes of its values

Even so, if I need to add a byte (bytes) for every position, for example, 1 for a one digit number, 2 for a two digit number and so on and an additional 1 byte as it is in case of Strings, I should add 230,850 to the total.

So it makes a new total of 645,301 + 230,850 = 876,153‬.

This is the maximum allowed. Adding 40,328, will be rejected.

Anyway it is again less than the maximum 1,048,576 allowed.

Upvotes: 1

Views: 3518

Answers (1)

Jay
Jay

Reputation: 35648

TL;DR

The original question did not involve Geopoints but as more discussion took place that was the ultimate goal. The issue is not that Firestore Documents can't hold 1Mb of data (because they can as clearly shown below) but the actual issue is how the OP is calculating how much data they want to store.

A Geopoint takes 16 bytes but there is also the rest of the calculation that should be added in. So here's a summary to calculate the size of a document

docNameSize = 8 //suppose it's called 'geoArray'
fieldNameSize = 5 // this is an array, so the first element name is 0 = 1 byte, 
                  // element 10 name would be two bytes
                  // up to 5 bytes for the higher numbers
geoPointSize = 16 * number of geopoints
addlSize = 32

So suppose there are 1000 geopoints

8 + (bytes depending on the field name length) + (16 * # of geopoints) + addl Size

So as you can see, the discussion is not around how much data a document will hold but about how the document size for a geopoint is calculated.

quick calculation

var s = ""
for i in 0..<10000 {
    s += String(i)
}
print(s.count)

shows that if you want to store 10000 Geopoints, 38890 bytes goes just to field names alone.

Discussion

This answer shows how to calculate the size of a Firestore document as well as the code to demonstrate how a file (an image in this case) of size 1Mb can be uploaded to a Firestore document.

Note that this is NOT how it should be done in real world use! - images and files should be stored in Storage, not Firestore, so take this as an example case.

An additional note that storing datasets that max out the capacity of a document may hinder overall performance and negates the ability to query or sort that data server side which puts a lot more strain on the apps resources. If there is concern about cost per number of writes/reads, I suggest looking at the Real Time Data Base as the costs are per amount of data, not reads/writes.

First we start with a 1Mb jpg called Mountain

enter image description here

To calculate the actual amount of data being uploaded, we use the following from the Firebase Storage and Calculations

The size of a document is the sum of:

  • The document name size
  • The sum of the string size of each field name
  • The sum of the size of each field value (we have only one field in
    this example)
  • 32 additional bytes

In the following code, the document name is 'mountain_image' which is 14, the field name is 'imageData' 9, the size of the field value is calculated (shown below) plus 32 bytes.

For this example, I've dragged the 1Mb image into my App bundle. Here's the (macOS) code that reads that image, converts it to a NSData type for Firestore and uploads the file.

func uploadImageToFirestre() {
    let image = NSImage(imageLiteralResourceName: "Mountain.jpeg")

    guard let asTiffData = image.tiffRepresentation else { return }
    let data = NSData(data: asTiffData)

    let imgRep = NSBitmapImageRep(data: data as Data)
    guard let jpgData = imgRep?.representation(using: NSBitmapImageRep.FileType.jpeg, properties:  [:]) else { return }

    let docNameSize = 14
    let fieldNameSize = 9
    let dataSize = jpgData.count
    let addlSize = 32

    let totalSize = docNameSize + fieldNameSize + dataSize + addlSize

    print("allowed size: \(1048487)")
    print("total size:   \(totalSize)")
    let imageCollection = self.db.collection("images")
    let thisImage = imageCollection.document("mountain_image")
    let dict:[String:Any] = ["imageData": jpgData]
    thisImage.setData(dict, completion: { error in
        if let err = error {
            print(err.localizedDescription)
            return
        }

        print("upload success")
    })
}

the output to console is this

allowed size: 1048487
total size:   1040221
upload success

So as can be seen, the total size is just under the allowed size in a Firestore document.

To summarize, this code uploads a 1Mb file to a Firestore Document

For completeness, here's the code that reads back that data object, converts back to an image and displays in the UI

func readImageFromFirestore() {
    let imageCollection = self.db.collection("images")
    imageCollection.getDocuments(completion: { snapshot, error in
        if let err = error {
            print(err.localizedDescription)
            return
        }

        guard let snap = snapshot else { return }

        for doc in snap.documents {
            let imageData = doc.get("imageData") as! Data
            let image = NSImage(data: imageData)
            self.myImageView.image = image
        }

    })
}

Keep in mind that Text Strings sizes are the number of UTF-8 encoded bytes + 1 so 'Hello' would be 6 total, 5 + 1

EDIT:

The OP added some additional information about storing Geopoints. A Geopoint is a specific data type in Firestore and requires a single field to store a geopoint. Attempting to store multiple geopoints in a single field is not an option.

That being said, if you want to store 1Mb of geopoints, it can still be done.

Here's some math: the total bytes allowed in a document is 1048487 and if each geopoint uses 16 bytes, quick division shows that approximately 65530 worth of geopoint data can be stored.

So if I can upload 65530 bytes then it shows that a document can hold approximately 1Mb of data. Right? Here's the code that does that

The following code creates almost 65530 geopoints, converts them to a string and stores them in a single Firestore document.

func uploadGeopoints() {
    var geoArray = [GeoPoint]()
    let point = GeoPoint(latitude: 1.0, longitude: 1.0)
    for i in 0..<65530 {
        geoArray.append(point)
    }

    let geoString = geoArray.map { String("\($0.latitude)\($0.longitude)") }

    let combinedString = geoString.joined()

    let geoCollection = self.db.collection("geoStrings")
    let thisGeoString = geoCollection.document()
    let dict:[String: Any] = ["geoString": combinedString]
    thisGeoString.setData(dict, completion: { error in
        if let err = error {
            print(err.localizedDescription)
            return
        }

        print("upload success")
    })
}

Upvotes: 1

Related Questions