11

The Story

I am using Firebase Storage in my app to upload large files into the Firebase Storage. Files are mostly videos which can be even larger than 2 GB some times.

What I Have Done

This is what I have done.

UploadTask originalUpload = originalDestination.putFile(Uri.fromFile(originalSource));
            mCurrentUploadTask = originalUpload;
            originalUpload.addOnProgressListener(mOnProgressUpdateListener);
originalUpload.addOnSuccessListener(mOriginalSuccessListener);

Just to inform, I am also converting these sync tasks to async as I need to process everything in the background using,

Tasks.await(originalUpload);

The Problem

The problem is weird and unexpected. The upload/download works perfectly, but it is very slow.

I am on a good internet connection of 1 MBps but these files are never transferred at this speed. It is around 100-150 KBps which is almost 15% of the available speed of my network.

I have tested this several times and on several different networks before making this claim. Nowhere, I found Firebase making full use of the available bandwidth, whereas other non-Firebase downloads/uploads works at full speed.

My app needs to download and upload large files, and therefore cannot afford to transfer at such slow speeds. This was not at all expected from Firebase.

Please let me know if I am doing anything wrong in my implementation or if it is an inherent problem with Firebase?

UPDATE

I have also been experiencing an issue with only the uploads. When ever I am downloading something and I just turn off the Wifi, the download gets cancelled with this error.

com.google.firebase.storage.StorageException: An unknown error occurred, please check the HTTP result code and inner exception for server response.

Read error: ssl=0xb7e7a510: I/O error during system call, Connection timed out
                                                                           javax.net.ssl.SSLException: Read error: ssl=0xb7e7a510: I/O error during system call, Connection timed out
                                                                               at com.android.org.conscrypt.NativeCrypto.SSL_read(Native Method)
                                                                               at com.android.org.conscrypt.OpenSSLSocketImpl$SSLInputStream.read(OpenSSLSocketImpl.java:699)
                                                                               at com.android.okio.Okio$2.read(Okio.java:113)
                                                                               at com.android.okio.RealBufferedSource.read(RealBufferedSource.java:48)
                                                                               at com.android.okhttp.internal.http.HttpConnection$FixedLengthSource.read(HttpConnection.java:446)
                                                                               at com.android.okio.RealBufferedSource$1.read(RealBufferedSource.java:168)
                                                                               at java.io.InputStream.read(InputStream.java:162)
                                                                               at com.google.firebase.storage.FileDownloadTask.run(Unknown Source)
                                                                               at com.google.firebase.storage.StorageTask$5.run(Unknown Source)
                                                                               at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
                                                                               at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
                                                                               at java.lang.Thread.run(Thread.java:818)

But when I do the exact same thing for uploads, it just waits for connection and if I turn on the Wifi again, it reconnects and resumes. Why does the problem only happen with downloading files? Is it a problem with getFile() API again?

FYI, I have not changed the timeout settings for both uploads and downloads. They are at their defaults.

Aritra Roy
  • 15,355
  • 10
  • 73
  • 107
  • For downloads, have you tried using the DownloadUrl directly instead of using Stream/FileDownloadTask? Is that any better? – Benjamin Wulfe Aug 21 '16 at 19:01
  • `videos which can be even larger than 2 GB some times` - oh boy. Why Firebase then? Try using some FTP server. – Reaz Murshed Aug 21 '16 at 19:40
  • @BenjaminWulfe The problem is not just with downloading, it is with both uploading and downloading. How can that be solved? – Aritra Roy Aug 22 '16 at 07:05
  • @ReazMurshed I am using almost all of the services offered by Firebase, that is why I chose to use the Firebase Storage. It is also backed by Google Cloud Storage, so I can enjoy all the benefits that it offers. Why should I not use Firebase Storage for large files? What are the downsides? – Aritra Roy Aug 22 '16 at 07:07
  • Firebase provides a server side implementation from a client side coding along with some other extraordinary features. Now if you had to write your own server side services, you might consider the large file uploads with some FTP server right? Firebase holds a websocket with its client side application which is a bit costly too. But anyway, it works great. – Reaz Murshed Aug 22 '16 at 07:38
  • @ReazMurshed Actually, I am not maintaining my own server. I had had done so, then FTP would be my choice for upload/downloads. Do you see any cons of using Firebase for large file storage and that also for a huge user base? – Aritra Roy Aug 22 '16 at 07:49
  • Yes, when the user base is huge you might have to consider response time from the server too. So I would not recommend uploading large files. So that you can optimize the connection with Firebase in other places too, to give smoother user experience. – Reaz Murshed Aug 22 '16 at 07:51
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/121511/discussion-between-aritra-roy-and-reaz-murshed). – Aritra Roy Aug 22 '16 at 08:07
  • i think it is all about metric unit (bit vs byte). 1Mbps = 1million bits/s which is about 125K bytes/s. – user1040495 Aug 12 '17 at 21:48

2 Answers2

3

I offer this not to refute your observations, but just as a point of comparison. As a test, I used the code below to upload a ~50MB file and time the progress. The test was run on a Samsung Galaxy S3 (4.4.2) with a home WiFi connection (in California). The average throughput was roughly 760KB/sec.

I ran the same test on other phone devices using the same network with throughput ranging from 300KB/sec to 850KB/sec (Moto X Pure; Marshmallow).

private void uploadTest() {
    // This file is ~50MB
    final File file = new File("/storage/emulated/0/DCIM/Camera/20160821_101145.mp4");
    // Upload the file
    Log.i(TAG, String.format("uploadTest: Starting upload of %5.2fMB file",
            file.length()/1024.0/1024.0));

    FirebaseStorage.getInstance().getReference("test").putFile(Uri.fromFile(file))
            .addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
                @Override
                public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                    Log.i(TAG, "onSuccess: Done");
                }
            }).addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {
        @Override
        public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {
            Log.i(TAG, String.format("onProgress: %5.2f MB transferred",
                    taskSnapshot.getBytesTransferred()/1024.0/1024.0));
        }
    });
}

This is the logcat produced, with some progress lines deleted to make the list more compact:

08-21 11:39:17.539 18427-18427/com.qbix.test I/MainActivity: uploadTest: Starting upload of 51.79MB file
08-21 11:39:17.559 18427-18427/com.qbix.test I/MainActivity: onProgress:  0.00 MB transferred
08-21 11:39:25.117 18427-18427/com.qbix.test I/MainActivity: onProgress:  5.00 MB transferred
08-21 11:39:31.654 18427-18427/com.qbix.test I/MainActivity: onProgress: 10.00 MB transferred
08-21 11:39:38.711 18427-18427/com.qbix.test I/MainActivity: onProgress: 15.00 MB transferred
08-21 11:39:45.088 18427-18427/com.qbix.test I/MainActivity: onProgress: 20.00 MB transferred
08-21 11:39:51.375 18427-18427/com.qbix.test I/MainActivity: onProgress: 25.00 MB transferred
08-21 11:39:57.411 18427-18427/com.qbix.test I/MainActivity: onProgress: 30.00 MB transferred
08-21 11:40:03.408 18427-18427/com.qbix.test I/MainActivity: onProgress: 35.00 MB transferred
08-21 11:40:10.886 18427-18427/com.qbix.test I/MainActivity: onProgress: 40.00 MB transferred
08-21 11:40:17.233 18427-18427/com.qbix.test I/MainActivity: onProgress: 45.00 MB transferred
08-21 11:40:23.069 18427-18427/com.qbix.test I/MainActivity: onProgress: 50.00 MB transferred
08-21 11:40:25.792 18427-18427/com.qbix.test I/MainActivity: onProgress: 51.79 MB transferred
08-21 11:40:25.792 18427-18427/com.qbix.test I/MainActivity: onSuccess: Done 
Bob Snyder
  • 37,759
  • 6
  • 111
  • 158
  • Thanks for the test. Btw, what is the total bandwidth of the network you are using to test? I never got more than 15% of the total bandwidth. – Aritra Roy Aug 22 '16 at 07:19
  • @AritraRoy: The Ookla speed test measured upload speed of 12 Mbits/sec. – Bob Snyder Aug 22 '16 at 14:11
  • I see. Its difficult to find out what exactly am I doing wrong. Can it be because I am using this in a background thread? – Aritra Roy Aug 22 '16 at 15:18
  • I doubt that is the cause, but don't know. The results I got on different devices indicates the device performance (memory, processors, etc) is a factor. Are you testing on a medium to high performance device? Also, I've found the Monitors pane (CPU, memory, network) in Android Studio provides a good overview of activity during testing. Watching it might give you some insights. – Bob Snyder Aug 22 '16 at 15:29
  • Aritra -- Can you also please test download speed when you download with the url returned by getDownloadUrl()? The reason this is important is it will give us (Google) an indication of whether the issue lies somewhere in our client sdk or if its something in the network routing. Also, please check to see if there are other processes running on your devices that might be taking CPU cycles. – Benjamin Wulfe Aug 22 '16 at 16:05
  • @qbix I have also tested on different devices ranging from low-end devices like Moto E to high-end devices OnePlus 3, but the speed is consistently poor in all of them. – Aritra Roy Aug 22 '16 at 18:28
  • @BenjaminWulfe I have used the download url and downloaded the file directly using the URL and this time it reaches the maximum speed. But if I try using getFile() API, its really slow every time. So, I think the problem is somewhere in the getFile() API. And I have also checked other running processes, its clean. I have also tested on several devices, on several Android versions, just to be fair. – Aritra Roy Aug 22 '16 at 18:30
2

I've been doing some testing on my end and I'm trying to get to the bottom of why you see bad perf. I have been able to replicate bad performance when using getStream doing reads of single bytes at a time but this can be fixed by doing a read into a reasonably size byte buffer (256kb buffer). After fixing this, the performance I see is equal to downloading directly with httpsUrlConnection.

Three possibilities pop into my mind:

1) you are seeing timeouts during dns resolution (to our multiple ips) and connection pooling isnt working during uploads. But this would only explain why uploads (which are chunked) would be slow. Downloads are not chunked.

2) you are in a region we have not yet optimized for. We are rolling out local support soon across asia and europe to optimized performance.

3) Maybe you are comparing it with something other than httpsurlconnection? Can you give more details of your test?

Here is my test:

        final long startTime = System.currentTimeMillis();
        // Test via Firebase
        mStorage.child(downloadPath).getStream(new StreamDownloadTask.StreamProcessor() {
               @Override
               public void doInBackground(StreamDownloadTask.TaskSnapshot taskSnapshot,
                      final InputStream inputStream) throws IOException {
                 readStream("FB", inputStream, startTime);
               }
        });

        //Test with AsyncTask
        AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... voids) {
                URL url = null;
                try {
                    url = new URL(uri.toString());
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                }
                try {
                    HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
                    connection.connect();
                    if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                        return null;
                    }
                    InputStream inputStream = connection.getInputStream();
                    readStream("ASYNC_TASK", inputStream, startTime);
                    inputStream.close();

                }
                catch(IOException e) {

                }

                return null;
            }
        };

        task.execute();

    private void readStream(String desc, InputStream inputStream, long startTime) {
      long bytesRead = 0;
      byte[] buffer = new byte[256*1024];
      int b = 1;
      try {
        while (b > 0) {
            b = inputStream.read(buffer);
            bytesRead += b;
                Log.i("giug", "PROGRESS_" + desc + ": bytesperMS:" +
                        (bytesRead / (System.currentTimeMillis() - startTime)));
        }
        inputStream.close();
      } catch (IOException e) {
          e.printStackTrace();
      }
   } 
Benjamin Wulfe
  • 1,705
  • 13
  • 9
  • But I have been using getFile() and not getStream(). Can you share your code so that I can implement it and respond back to you with the results? And I have been experience a very weird issue and have updated my question. Can you please help on this? – Aritra Roy Aug 29 '16 at 06:07
  • your question about retries on download is a different issue. So to confirm -- the performance issue is only on uploads? – Benjamin Wulfe Aug 30 '16 at 13:24
  • No, the problem of slow transfer speeds is with both. I have asked a separate question on the retires, http://stackoverflow.com/questions/38993429/firebase-storage-downloads-doesnt-resume-on-network-reconnection. Please help solving this problem if possible. There seems several issues with Firebase Storage, if its this buggy then I will have to stop using it in my app. – Aritra Roy Aug 30 '16 at 15:36
  • I've added my test code which for me shows identical results between async task and through firebase. I wouldnt run them at the same time. – Benjamin Wulfe Aug 31 '16 at 18:59